Merge pull request #130 from Nheko-Reborn/0.7.0-dev

0.7.0 dev merge to master
This commit is contained in:
Joseph Donofry 2020-02-28 19:10:39 -05:00 committed by GitHub
commit 30cb7c5b02
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
244 changed files with 30910 additions and 11811 deletions

View file

@ -11,5 +11,7 @@ FILES=$(find src -type f -type f \( -iname "*.cpp" -o -iname "*.h" \))
for f in $FILES
do
clang-format -i "$f" && git diff --exit-code
clang-format -i "$f"
done;
git diff --exit-code

View file

@ -2,14 +2,13 @@
set -ex
if [ "$FLATPAK" ]; then
flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak --noninteractive install --user flathub org.kde.Platform//5.14
flatpak --noninteractive install --user flathub org.kde.Sdk//5.14
exit
fi
if [ "$TRAVIS_OS_NAME" = "osx" ]; then
brew update
brew install qt5 lmdb clang-format ninja libsodium cmark
brew upgrade boost cmake icu4c || true
brew tap nlohmann/json
brew install --with-cmake nlohmann_json
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
sudo python get-pip.py
@ -21,34 +20,12 @@ fi
if [ "$TRAVIS_OS_NAME" = "linux" ]; then
sudo update-alternatives --install /usr/bin/gcc gcc "/usr/bin/${CC}" 10
sudo update-alternatives --install /usr/bin/g++ g++ "/usr/bin/${CXX}" 10
if [ -z "$QT_VERSION" ]; then
QT_VERSION="592"
QT_PKG="59"
fi
wget https://cmake.org/files/v3.12/cmake-3.12.2-Linux-x86_64.sh
sudo sh cmake-3.12.2-Linux-x86_64.sh --skip-license --prefix=/usr/local
mkdir -p build-libsodium
( cd build-libsodium
curl -L https://download.libsodium.org/libsodium/releases/libsodium-1.0.16.tar.gz -o libsodium-1.0.16.tar.gz
tar xfz libsodium-1.0.16.tar.gz
cd libsodium-1.0.16/
./configure && make && make check && sudo make install )
sudo add-apt-repository -y ppa:beineri/opt-qt${QT_VERSION}-trusty
# needed for git-lfs, otherwise the follow apt update fails.
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 6B05F25D762E3157
# needed for mongodb repository: https://github.com/travis-ci/travis-ci/issues/9037
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6
sudo apt update -qq
sudo apt install -qq -y \
qt${QT_PKG}base \
qt${QT_PKG}tools \
qt${QT_PKG}svg \
qt${QT_PKG}multimedia \
liblmdb-dev
sudo update-alternatives --set gcc "/usr/bin/${CC}"
sudo update-alternatives --set g++ "/usr/bin/${CXX}"
wget https://cmake.org/files/v3.15/cmake-3.15.5-Linux-x86_64.sh
sudo sh cmake-3.15.5-Linux-x86_64.sh --skip-license --prefix=/usr/local
fi

View file

@ -25,8 +25,8 @@ for iconSize in 16 32 48 64 128 256 512; do
done
# Only download the file when not already present
if ! [ -f linuxdeployqt-continuous-x86_64.AppImage ] ; then
wget -c "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage"
if ! [ -f linuxdeployqt-6-x86_64.AppImage ] ; then
wget -c "https://github.com/probonopd/linuxdeployqt/releases/download/6/linuxdeployqt-6-x86_64.AppImage"
fi
chmod a+x linuxdeployqt*.AppImage
@ -36,7 +36,7 @@ unset LD_LIBRARY_PATH
ARCH=$(uname -m)
export ARCH
LD_LIBRARY_PATH=$(pwd)/.deps/usr/lib/:$LD_LIBRARY_PATH
LD_LIBRARY_PATH=$(pwd)/.deps/usr/lib/:/usr/local/lib/:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH
for res in ./linuxdeployqt*.AppImage
@ -44,12 +44,14 @@ do
linuxdeployqt=$res
done
./"$linuxdeployqt" ${DIR}/usr/share/applications/*.desktop -unsupported-allow-new-glibc -bundle-non-qt-libs
./"$linuxdeployqt" ${DIR}/usr/share/applications/*.desktop -unsupported-allow-new-glibc -appimage
./"$linuxdeployqt" ${DIR}/usr/share/applications/*.desktop -unsupported-allow-new-glibc -bundle-non-qt-libs -qmldir=./resources/qml -appimage
chmod +x nheko-*x86_64.AppImage
if [ ! -z "$VERSION" ]; then
mkdir artifacts
cp nheko-*x86_64.AppImage artifacts/
if [ -n "$VERSION" ]; then
# commented out for now, as AppImage file appears to already contain the version.
#mv nheko-*x86_64.AppImage nheko-${VERSION}-x86_64.AppImage
echo "nheko-${VERSION}-x86_64.AppImage"

View file

@ -16,7 +16,7 @@ PATH=/usr/local/opt/qt/bin/:${PATH}
mkdir -p nheko.app/Contents/Frameworks
find "${ICU_LIB}" -type l -name "*.dylib" -exec cp -a -n {} nheko.app/Contents/Frameworks/ \; || true
sudo macdeployqt nheko.app -dmg -always-overwrite
sudo macdeployqt nheko.app -dmg -always-overwrite -qmldir=../resources/qml/
user=$(id -nu)
sudo chown "${user}" nheko.dmg
@ -25,6 +25,8 @@ PATH=/usr/local/opt/qt/bin/:${PATH}
dmgbuild -s ./.ci/macos/settings.json "Nheko" nheko.dmg
if [ ! -z "$VERSION" ]; then
if [ -n "$VERSION" ]; then
mv nheko.dmg "nheko-${VERSION}.dmg"
mkdir artifacts
cp "nheko-${VERSION}.dmg" artifacts/
fi

View file

@ -2,17 +2,25 @@
set -ex
if [ "$FLATPAK" ]; then
mkdir -p build-flatpak
cd build-flatpak
flatpak-builder --ccache --repo=repo --subject="Build of Nheko ${VERSION} `date`" app ../io.github.NhekoReborn.Nheko.json
flatpak build-bundle repo nheko-${VERSION}-${ARCH}.flatpak io.github.NhekoReborn.Nheko 0.7.0-dev
mkdir ../artifacts
mv nheko-*.flatpak ../artifacts
exit
fi
if [ "$TRAVIS_OS_NAME" = "linux" ]; then
export CC=${C_COMPILER}
export CXX=${CXX_COMPILER}
# make build use all available cores
export CMAKE_BUILD_PARALLEL_LEVEL=$(cat /proc/cpuinfo | awk '/^processor/{print $3}' | wc -l)
sudo update-alternatives --install /usr/bin/gcc gcc "/usr/bin/${C_COMPILER}" 10
sudo update-alternatives --install /usr/bin/g++ g++ "/usr/bin/${CXX_COMPILER}" 10
sudo update-alternatives --set gcc "/usr/bin/${C_COMPILER}"
sudo update-alternatives --set g++ "/usr/bin/${CXX_COMPILER}"
export PATH="/usr/local/bin/:${PATH}"
cmake --version
fi
if [ "$TRAVIS_OS_NAME" = "linux" ]; then
@ -24,27 +32,39 @@ if [ "$TRAVIS_OS_NAME" = "osx" ]; then
export CMAKE_PREFIX_PATH=/usr/local/opt/qt5
fi
# Build & install dependencies
cmake -GNinja -Hdeps -B.deps \
-DUSE_BUNDLED_BOOST="${USE_BUNDLED_BOOST}" \
-DUSE_BUNDLED_CMARK="${USE_BUNDLED_CMARK}" \
-DUSE_BUNDLED_JSON="${USE_BUNDLED_JSON}"
cmake --build .deps
mkdir -p .deps/usr .hunter
# Build nheko
if [ "$TRAVIS_OS_NAME" = "osx" ]; then
cmake -GNinja -H. -Bbuild \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=.deps/usr
-DCMAKE_INSTALL_PREFIX=.deps/usr \
-DHUNTER_ROOT=".hunter" \
-DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF \
-DCMAKE_BUILD_TYPE=RelWithDebInfo -DHUNTER_CONFIGURATION_TYPES=RelWithDebInfo \
-DCMAKE_PREFIX_PATH=/usr/local/opt/qt5 \
-DCI_BUILD=ON
else
cmake -GNinja -H. -Bbuild \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=.deps/usr \
-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
if [ "$TRAVIS_OS_NAME" = "osx" ]; then
make lint;
if [ "$DEPLOYMENT" = 1 ] && [ ! -z "$VERSION" ] ; then
if [ "$DEPLOYMENT" = 1 ] && [ -n "$VERSION" ] ; then
make macos-deploy;
fi
fi
if [ "$TRAVIS_OS_NAME" = "linux" ] && [ "$DEPLOYMENT" = 1 ] && [ ! -z "$VERSION" ]; then
if [ "$TRAVIS_OS_NAME" = "linux" ] && [ "$DEPLOYMENT" = 1 ] && [ -n "$VERSION" ]; then
make linux-deploy;
fi

View file

@ -20,9 +20,9 @@ If you're planning to work on a new feature leave a message on the Matrix room
Example for a Japanese translation.
- Create a new translation file using the prototype in English
- e.g `cp resources/langs/nheko_en.ts resources/langs/nheko_jp.ts`
- e.g `cp resources/langs/nheko_en.ts resources/langs/nheko_ja.ts`
- Open the new translation file and change the line regarding the locale to reflect the current language.
- e.g `<TS version="2.1" language="en">` => `<TS version="2.1" language="jp">`
- e.g `<TS version="2.1" language="en">` => `<TS version="2.1" language="ja">`
- Run `make update-translations` to update the translation files with any missing text.
- Fill out the translation file (Qt Linguist can make things easier).
- Submit a PR!

16
.gitignore vendored
View file

@ -1,7 +1,16 @@
build
/build*
tags
cscope*
.clang_complete
*wintoastlib*
/.ccls-cache
/.exrc
.gdb_history
# GTAGS
GTAGS
GRTAGS
GPATH
# C++ objects and libs
@ -31,6 +40,7 @@ moc_*.cpp
qrc_*.cpp
ui_*.h
*-build-*
/.clangd/
# QtCreator
@ -43,6 +53,10 @@ ui_*.h
*.qmlproject.user
*.qmlproject.user.*
# Vim
*.swp
*.swo
#####=== CMake ===#####
CMakeCache.txt

View file

@ -1,73 +1,138 @@
language: cpp
sudo: required
dist: trusty
dist: xenial
notifications:
webhooks:
urls:
- https://scalar.vector.im/api/neb/services/hooks/dHJhdmlzLWNpLyU0MHJlZF9za3klM0FvY2Vhbi5qb2Vkb25vZnJ5LmNvbS8lMjFldkFxa1BIWnVQSElHZWVuaGklM0FvY2Vhbi5qb2Vkb25vZnJ5LmNvbQ
- https://scalar.vector.im/api/neb/services/hooks/dHJhdmlzLWNpLyU0MHJlZF9za3klM0FuaGVrby5pbS8lMjFVYkNtSWxHVEhOSWdJUlpjcHQlM0FuaGVrby5pbQ
on_success: always
on_failure: always
on_start: never
email: false
cache:
directories:
- .hunter
- build-flatpak/.flatpak-builder
matrix:
include:
- os: osx
compiler: clang
osx_image: xcode9
# C++17 support
osx_image: xcode10.2
env:
- DEPLOYMENT=1
- USE_BUNDLED_BOOST=0
- USE_BUNDLED_CMARK=0
- USE_BUNDLED_JSON=0
addons:
homebrew:
taps: nlohmann/json
packages:
- clang-format
- cmake
- ninja
- openssl
- qt5
update: true # workaround for broken travis homebrew
- os: linux
compiler: gcc
compiler: gcc-7
env:
- CXX_COMPILER=g++-5
- C_COMPILER=gcc-5
- QT_VERSION="-5.10.1"
- QT_PKG=510
- CXX=g++-7
- CC=gcc-7
- QT_PKG=512
- DEPLOYMENT=1
- USE_BUNDLED_BOOST=1
- USE_BUNDLED_CMARK=1
- USE_BUNDLED_JSON=1
addons:
apt:
sources: ["ubuntu-toolchain-r-test"]
packages: ["g++-5", "ninja-build"]
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:beineri/opt-qt-5.12.6-xenial'
packages:
- g++-7
- ninja-build
- qt512base
- qt512tools
- qt512svg
- qt512multimedia
- qt512quickcontrols2
- qt512graphicaleffects
- liblmdb-dev
- libgl1-mesa-dev # needed for missing gl.h
- os: linux
compiler: gcc
compiler: gcc-8
env:
- CXX_COMPILER=g++-8
- C_COMPILER=gcc-8
- QT_VERSION=571
- QT_PKG=57
- USE_BUNDLED_BOOST=1
- USE_BUNDLED_CMARK=1
- USE_BUNDLED_JSON=1
addons:
apt:
sources: ["ubuntu-toolchain-r-test"]
packages: ["g++-8", "ninja-build"]
- os: linux
compiler: clang
env:
- CXX_COMPILER=clang++-5.0
- C_COMPILER=clang-5.0
- QT_VERSION=592
- CXX=g++-8
- CC=gcc-8
- QT_PKG=59
- USE_BUNDLED_BOOST=1
- USE_BUNDLED_CMARK=1
- USE_BUNDLED_JSON=1
addons:
apt:
sources: ["ubuntu-toolchain-r-test", "llvm-toolchain-trusty-5.0"]
packages: ["clang-5.0", "g++-7", "ninja-build"]
sources:
- ubuntu-toolchain-r-test
- sourceline: 'ppa:beineri/opt-qt597-xenial'
packages:
- g++-8
- ninja-build
- qt59base
- qt59tools
- qt59svg
- qt59multimedia
- qt59quickcontrols2
- qt59graphicaleffects
- liblmdb-dev
- libgl1-mesa-dev # needed for missing gl.h
- os: linux
compiler: clang-6
env:
- CXX=clang++-6.0
- CC=clang-6.0
- QT_PKG=59
addons:
apt:
sources:
- ubuntu-toolchain-r-test
- llvm-toolchain-xenial-6.0
- sourceline: 'ppa:beineri/opt-qt597-xenial'
packages:
- clang++-6.0
- g++-7
- ninja-build
- qt59base
- qt59tools
- qt59svg
- qt59multimedia
- qt59quickcontrols2
- qt59graphicaleffects
- liblmdb-dev
- libgl1-mesa-dev # needed for missing gl.h
- os: linux
env:
- DEPLOYMENT=1
- FLATPAK=1
- ARCH=amd64
addons:
apt:
sources:
- sourceline: 'ppa:alexlarsson/flatpak'
packages:
- flatpak
- flatpak-builder
- elfutils
- os: linux
arch: arm64
env:
- DEPLOYMENT=1
- FLATPAK=1
- ARCH=arm64
addons:
apt:
sources:
- sourceline: 'ppa:alexlarsson/flatpak'
packages:
- flatpak
- flatpak-builder
- elfutils
- librsvg2-bin
before_install:
- export CXX=${CXX_COMPILER}
- export CC=${C_COMPILER}
# Use TRAVIS_TAG if defined, or the short commit SHA otherwise
- export VERSION=${TRAVIS_TAG:-$(git rev-parse --short HEAD)}
install:
@ -79,6 +144,21 @@ script:
- sed -i -e "s/VERSION_NAME_VALUE/${VERSION}/g" ./.ci/bintray-release.json || true
- cp ./.ci/bintray-release.json .
deploy:
- provider: s3
access_key_id: $ARTIFACTS_KEY
secret_access_key: $ARTIFACTS_SECRET
bucket: $ARTIFACTS_BUCKET
region: $AWS_DEFAULT_REGION
detect_encoding: true
cache_control: "max-age=31536000"
skip_cleanup: true
acl: public_read
local_dir: artifacts
on:
condition: "$DEPLOYMENT == 1"
repo: Nheko-Reborn/nheko
tags: false
all_branches: true
- provider: bintray
user: "redsky17"
key:

View file

@ -2,6 +2,25 @@
## [Unreleased]
### [0.7.0] -- Unreleased
0.7.0 *requires* mtxclient 0.3.0. Make sure you compile against 0.3.0
if you do not use the mtxclient bundled with nheko.
#### Features
- Make nheko session import / export format match riot. Fixes #48 (WIP)
- Implement proper replies (WIP)
- Add .well-known support for auto-completing homeserver information
- Add mentions viewer so you can see all the messages you have been mentioned in (WIP)
- Add emoji font selection preference
#### Improvements
- Add dedicated reply button to Timeline items. Add button for other options so
that right click isn't always required.
- Fix various things with regards to emoji rendering and the emoji picker (WIP)
- Lots and lots and lots of localization updates.
- Additional tweaks to the system theme
## [0.6.4] - 2019-05-22
*Most* of the below fixes are due to updates in mtxclient. Make sure you compile against 0.2.1

View file

@ -1,22 +1,86 @@
cmake_minimum_required(VERSION 3.11)
cmake_minimum_required(VERSION 3.13)
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)
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
set(
CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_LIST_DIR}/toolchain.cmake"
CACHE
FILEPATH "Default toolchain"
)
add_definitions(-DBOOST_MPL_LIMIT_LIST_SIZE=30)
add_definitions(-DBOOST_MPL_CFG_NO_PREPROCESSED_HEADERS)
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
)
option(USE_BUNDLED_BOOST "Use the bundled version of Boost." ${HUNTER_ENABLED})
option(USE_BUNDLED_SPDLOG "Use the bundled version of spdlog."
${HUNTER_ENABLED})
option(USE_BUNDLED_OLM "Use the bundled version of libolm." ${HUNTER_ENABLED})
option(USE_BUNDLED_GTEST "Use the bundled version of Google Test."
${HUNTER_ENABLED})
option(USE_BUNDLED_CMARK "Use the bundled version of cmark."
${HUNTER_ENABLED})
option(USE_BUNDLED_JSON "Use the bundled version of nlohmann json."
${HUNTER_ENABLED})
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_ZLIB "Use the bundled version of zlib."
${HUNTER_ENABLED})
option(USE_BUNDLED_LMDB "Use the bundled version of lmdb."
${HUNTER_ENABLED})
option(USE_BUNDLED_LMDBXX "Use the bundled version of lmdb++."
${HUNTER_ENABLED})
option(USE_BUNDLED_TWEENY "Use the bundled version of tweeny."
${HUNTER_ENABLED})
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
if(${CMAKE_VERSION} VERSION_LESS "3.14.0")
message("Adding FetchContent_MakeAvailable")
# from cmakes sources
macro(FetchContent_MakeAvailable)
foreach(contentName IN ITEMS ${ARGV})
string(TOLOWER ${contentName} contentNameLower)
FetchContent_GetProperties(${contentName})
if(NOT ${contentNameLower}_POPULATED)
FetchContent_Populate(${contentName})
# Only try to call add_subdirectory() if the populated content
# can be treated that way. Protecting the call with the check
# allows this function to be used for projects that just want
# to ensure the content exists, such as to provide content at
# a known location.
if(EXISTS ${${contentNameLower}_SOURCE_DIR}/CMakeLists.txt)
add_subdirectory(${${contentNameLower}_SOURCE_DIR}
${${contentNameLower}_BINARY_DIR})
endif()
endif()
endforeach()
endmacro()
endif()
include(GNUInstallDirs)
# Include Qt basic functions
include(QtCommon)
project(nheko LANGUAGES C CXX)
project(nheko LANGUAGES CXX C)
set(CPACK_PACKAGE_VERSION_MAJOR "0")
set(CPACK_PACKAGE_VERSION_MINOR "6")
set(CPACK_PACKAGE_VERSION_PATCH "4")
set(CPACK_PACKAGE_VERSION_MINOR "7")
set(CPACK_PACKAGE_VERSION_PATCH "0")
set(PROJECT_VERSION_MAJOR ${CPACK_PACKAGE_VERSION_MAJOR})
set(PROJECT_VERSION_MINOR ${CPACK_PACKAGE_VERSION_MINOR})
set(PROJECT_VERSION_PATCH ${CPACK_PACKAGE_VERSION_PATCH})
@ -27,15 +91,11 @@ fix_project_version()
# Set additional project information
set(COMPANY "Nheko")
set(COPYRIGHT "Copyright (c) 2018 Nheko Contributors")
set(COPYRIGHT "Copyright (c) 2019 Nheko Contributors")
set(IDENTIFIER "com.github.mujx.nheko")
add_project_meta(META_FILES_TO_INCLUDE)
if(APPLE)
set(OPENSSL_ROOT_DIR /usr/local/opt/openssl)
endif()
if(NOT MSVC AND NOT APPLE)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
@ -64,12 +124,19 @@ endif()
#
# LMDB
#
include(LMDB)
#include(LMDB)
if(USE_BUNDLED_LMDB)
hunter_add_package(lmdb)
find_package(liblmdb CONFIG REQUIRED)
else()
find_package(LMDB)
endif()
#
# Discover Qt dependencies.
#
find_package(Qt5 COMPONENTS Core Widgets LinguistTools Concurrent Svg Multimedia REQUIRED)
find_package(Qt5 COMPONENTS Core Widgets LinguistTools Concurrent Svg Multimedia Qml QuickControls2 QuickWidgets REQUIRED)
find_package(Qt5QuickCompiler)
find_package(Qt5DBus)
if (APPLE)
@ -77,21 +144,12 @@ if (APPLE)
endif(APPLE)
if (Qt5Widgets_FOUND)
if (Qt5Widgets_VERSION VERSION_LESS 5.7.0)
if (Qt5Widgets_VERSION VERSION_LESS 5.9.0)
message(STATUS "Qt version ${Qt5Widgets_VERSION}")
message(WARNING "Minimum supported Qt5 version is 5.7!")
message(WARNING "Minimum supported Qt5 version is 5.9!")
endif()
endif(Qt5Widgets_FOUND)
#
# Set up compiler flags.
#
if (NOT MSVC)
set(CMAKE_C_COMPILER gcc)
endif(NOT MSVC)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
if(NOT MSVC)
set(
@ -99,13 +157,12 @@ if(NOT MSVC)
"${CMAKE_CXX_FLAGS} \
-Wall \
-Wextra \
-Werror \
-pipe \
-pedantic \
-fsized-deallocation \
-fdiagnostics-color=always \
-Wunreachable-code \
-std=c++14"
-std=c++17"
)
if (NOT CMAKE_COMPILER_IS_GNUCXX)
# -Wshadow is buggy and broken in GCC, so do not enable it.
@ -171,17 +228,18 @@ configure_file(cmake/nheko.h config/nheko.h)
set(SRC_FILES
# Dialogs
src/dialogs/CreateRoom.cpp
src/dialogs/FallbackAuth.cpp
src/dialogs/ImageOverlay.cpp
src/dialogs/PreviewUploadOverlay.cpp
src/dialogs/InviteUsers.cpp
src/dialogs/JoinRoom.cpp
src/dialogs/MemberList.cpp
src/dialogs/LeaveRoom.cpp
src/dialogs/Logout.cpp
src/dialogs/UserProfile.cpp
src/dialogs/ReadReceipts.cpp
src/dialogs/MemberList.cpp
src/dialogs/PreviewUploadOverlay.cpp
src/dialogs/ReCaptcha.cpp
src/dialogs/ReadReceipts.cpp
src/dialogs/RoomSettings.cpp
src/dialogs/UserProfile.cpp
# Emoji
src/emoji/Category.cpp
@ -192,16 +250,13 @@ set(SRC_FILES
# Timeline
src/timeline/TimelineViewManager.cpp
src/timeline/TimelineItem.cpp
src/timeline/TimelineView.cpp
src/timeline/widgets/AudioItem.cpp
src/timeline/widgets/FileItem.cpp
src/timeline/widgets/ImageItem.cpp
src/timeline/widgets/VideoItem.cpp
src/timeline/TimelineModel.cpp
src/timeline/DelegateChooser.cpp
# UI components
src/ui/Avatar.cpp
src/ui/Badge.cpp
src/ui/DropShadow.cpp
src/ui/LoadingIndicator.cpp
src/ui/InfoMessage.cpp
src/ui/FlatButton.cpp
@ -224,24 +279,28 @@ set(SRC_FILES
src/ChatPage.cpp
src/CommunitiesListItem.cpp
src/CommunitiesList.cpp
src/EventAccessors.cpp
src/InviteeItem.cpp
src/LoginPage.cpp
src/Logging.cpp
src/MainWindow.cpp
src/MatrixClient.cpp
src/MxcImageProvider.cpp
src/ColorImageProvider.cpp
src/QuickSwitcher.cpp
src/Olm.cpp
src/RegisterPage.cpp
src/RoomInfoListItem.cpp
src/RoomList.cpp
src/RunGuard.cpp
src/SideBarActions.cpp
src/Splitter.cpp
src/SuggestionsPopup.cpp
src/popups/SuggestionsPopup.cpp
src/popups/PopupItem.cpp
src/popups/ReplyPopup.cpp
src/popups/UserMentions.cpp
src/TextInputWidget.cpp
src/TopRoomBar.cpp
src/TrayIcon.cpp
src/TypingDisplay.cpp
src/Utils.cpp
src/UserInfoWidget.cpp
src/UserSettingsPage.cpp
@ -249,29 +308,80 @@ set(SRC_FILES
src/main.cpp
)
# ExternalProject dependencies
set(EXTERNAL_PROJECT_DEPS "")
include(FeatureSummary)
set(Boost_USE_STATIC_LIBS OFF)
set(Boost_USE_STATIC_RUNTIME OFF)
set(Boost_USE_MULTITHREADED ON)
find_package(Boost 1.66 REQUIRED
COMPONENTS atomic
chrono
date_time
iostreams
random
regex
if(USE_BUNDLED_BOOST)
hunter_add_package(Boost COMPONENTS iostreams system thread)
endif()
find_package(Boost 1.70 REQUIRED
COMPONENTS iostreams
system
thread)
if(USE_BUNDLED_ZLIB)
hunter_add_package(ZLIB)
endif()
find_package(ZLIB REQUIRED)
if(USE_BUNDLED_OPENSSL)
hunter_add_package(OpenSSL)
endif()
find_package(OpenSSL REQUIRED)
find_package(MatrixClient 0.1.0 REQUIRED)
find_package(Olm 2 REQUIRED)
if(USE_BUNDLED_MTXCLIENT)
include(FetchContent)
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
FetchContent_Declare(
MatrixClient
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
GIT_TAG 7fc1d357afaabb134cb6d9c593f94915973d31fa
)
FetchContent_MakeAvailable(MatrixClient)
else()
find_package(MatrixClient 0.3.0 REQUIRED)
endif()
if(USE_BUNDLED_OLM)
include(FetchContent)
set(OLM_TESTS OFF CACHE INTERNAL "")
FetchContent_Declare(
Olm
GIT_REPOSITORY https://gitlab.matrix.org/matrix-org/olm.git
GIT_TAG 3.1.4
)
FetchContent_MakeAvailable(Olm)
else()
find_package(Olm 3)
set_package_properties(Olm PROPERTIES
DESCRIPTION "An implementation of the Double Ratchet cryptographic ratchet"
URL "https://git.matrix.org/git/olm/about/"
TYPE REQUIRED
)
endif()
if(USE_BUNDLED_SPDLOG)
hunter_add_package(spdlog)
endif()
find_package(spdlog 1.0.0 CONFIG REQUIRED)
if(USE_BUNDLED_CMARK)
include(FetchContent)
FetchContent_Declare(
cmark
GIT_REPOSITORY https://github.com/commonmark/cmark.git
GIT_TAG 242e277a661ec7e51f34dcaf86c1925d550b1498 #0.29.0 << doesn't work with fetch content yet
CMAKE_ARGS "CMARK_STATIC=ON CMARK_SHARED=OFF CMARK_TESTS=OFF CMARK_TESTS=OFF"
)
FetchContent_MakeAvailable(cmark)
if (MSVC)
add_library(cmark::cmark ALIAS libcmark)
else()
add_library(cmark::cmark ALIAS libcmark_static)
endif()
else()
find_package(cmark REQUIRED)
endif()
if(USE_BUNDLED_JSON)
hunter_add_package(nlohmann_json)
endif()
find_package(nlohmann_json 3.2.0)
set_package_properties(nlohmann_json PROPERTIES
DESCRIPTION "JSON for Modern C++, a C++11 header-only JSON class"
@ -279,6 +389,10 @@ set_package_properties(nlohmann_json PROPERTIES
TYPE REQUIRED
)
if(USE_BUNDLED_LMDBXX)
hunter_add_package(lmdbxx)
find_package(lmdbxx CONFIG REQUIRED)
else()
if(NOT LMDBXX_INCLUDE_DIR)
find_path(LMDBXX_INCLUDE_DIR
NAMES lmdb++.h
@ -286,42 +400,47 @@ if(NOT LMDBXX_INCLUDE_DIR)
/usr/local/include
$ENV{LIB_DIR}/include
$ENV{LIB_DIR}/include/lmdbxx)
endif()
include_directories(SYSTEM ${LMDBXX_INCLUDE_DIR})
if(NOT TWEENY_INCLUDE_DIR)
find_path(TWEENY_INCLUDE_DIR
NAMES tweeny/tweeny.h
PATHS /usr/include/
/usr/local/include/
$ENV{LIB_DIR}/include/
$ENV{LIB_DIR}/include/tweeny)
add_library(lmdbxx INTERFACE)
target_include_directories(lmdbxx INTERFACE ${LMDBXX_INCLUDE_DIR})
add_library(lmdbxx::lmdbxx ALIAS lmdbxx)
endif()
include_directories(SYSTEM ${TWEENY_INCLUDE_DIR})
include_directories(${CMAKE_SOURCE_DIR}/src)
include_directories(${Boost_INCLUDE_DIRS})
if(USE_BUNDLED_TWEENY)
include(FetchContent)
FetchContent_Declare(
Tweeny
GIT_REPOSITORY https://github.com/mobius3/tweeny.git
GIT_TAG 6a5033372fe53c4c731c66c8a2d56261746cd85c #v3 <- v3 has unfixed warnings
)
FetchContent_MakeAvailable(Tweeny)
else()
find_package(Tweeny REQUIRED)
endif()
# local inclue directory
include_directories(includes)
# single instance functionality
set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication")
add_subdirectory(third_party/SingleApplication-3.0.19/)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
qt5_wrap_cpp(MOC_HEADERS
# Dialogs
src/dialogs/CreateRoom.h
src/dialogs/FallbackAuth.h
src/dialogs/ImageOverlay.h
src/dialogs/PreviewUploadOverlay.h
src/dialogs/InviteUsers.h
src/dialogs/JoinRoom.h
src/dialogs/MemberList.h
src/dialogs/LeaveRoom.h
src/dialogs/Logout.h
src/dialogs/UserProfile.h
src/dialogs/MemberList.h
src/dialogs/PreviewUploadOverlay.h
src/dialogs/RawMessage.h
src/dialogs/ReadReceipts.h
src/dialogs/ReCaptcha.h
src/dialogs/ReadReceipts.h
src/dialogs/RoomSettings.h
src/dialogs/UserProfile.h
# Emoji
src/emoji/Category.h
@ -330,13 +449,9 @@ qt5_wrap_cpp(MOC_HEADERS
src/emoji/PickButton.h
# Timeline
src/timeline/TimelineItem.h
src/timeline/TimelineView.h
src/timeline/TimelineViewManager.h
src/timeline/widgets/AudioItem.h
src/timeline/widgets/FileItem.h
src/timeline/widgets/ImageItem.h
src/timeline/widgets/VideoItem.h
src/timeline/TimelineModel.h
src/timeline/DelegateChooser.h
# UI components
src/ui/Avatar.h
@ -361,13 +476,13 @@ qt5_wrap_cpp(MOC_HEADERS
src/notifications/Manager.h
src/AvatarProvider.h
src/Cache.h
src/Cache_p.h
src/ChatPage.h
src/CommunitiesListItem.h
src/CommunitiesList.h
src/LoginPage.h
src/MainWindow.h
src/MatrixClient.h
src/MxcImageProvider.h
src/InviteeItem.h
src/QuickSwitcher.h
src/RegisterPage.h
@ -375,11 +490,13 @@ qt5_wrap_cpp(MOC_HEADERS
src/RoomList.h
src/SideBarActions.h
src/Splitter.h
src/SuggestionsPopup.h
src/popups/SuggestionsPopup.h
src/popups/ReplyPopup.h
src/popups/PopupItem.h
src/popups/UserMentions.h
src/TextInputWidget.h
src/TopRoomBar.h
src/TrayIcon.h
src/TypingDisplay.h
src/UserInfoWidget.h
src/UserSettingsPage.h
src/WelcomePage.h
@ -391,22 +508,6 @@ qt5_wrap_cpp(MOC_HEADERS
include(Translations)
set(TRANSLATION_DEPS ${LANG_QRC} ${QRC} ${QM_SRC})
set(COMMON_LIBS
MatrixClient::MatrixClient
${Boost_LIBRARIES}
cmark::cmark
Qt5::Widgets
Qt5::Svg
Qt5::Concurrent
Qt5::Multimedia
nlohmann_json::nlohmann_json)
if(APPVEYOR_BUILD)
set(NHEKO_LIBS ${COMMON_LIBS} lmdb)
else()
set(NHEKO_LIBS ${COMMON_LIBS} ${LMDB_LIBRARY})
endif()
if (APPLE)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework Foundation -framework Cocoa")
set(SRC_FILES ${SRC_FILES} src/notifications/ManagerMac.mm src/emoji/MacHelper.mm)
@ -437,21 +538,55 @@ if(ASAN)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined")
endif()
add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS})
if(APPLE)
add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS})
target_link_libraries (nheko ${NHEKO_LIBS} Qt5::MacExtras)
target_link_libraries (nheko PRIVATE Qt5::MacExtras)
elseif(WIN32)
add_executable (nheko ${OS_BUNDLE} ${ICON_FILE} ${NHEKO_DEPS})
target_link_libraries (nheko ${NTDLIB} ${NHEKO_LIBS} Qt5::WinMain)
target_compile_definitions(nheko PRIVATE WIN32_LEAN_AND_MEAN)
target_link_libraries (nheko PRIVATE ${NTDLIB} Qt5::WinMain)
else()
add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS})
target_link_libraries (nheko ${NHEKO_LIBS} Qt5::DBus)
target_link_libraries (nheko PRIVATE Qt5::DBus)
endif()
target_include_directories(nheko PRIVATE src includes)
target_link_libraries(nheko PRIVATE
MatrixClient::MatrixClient
Boost::iostreams
Boost::system
Boost::thread
cmark::cmark
spdlog::spdlog
Qt5::Widgets
Qt5::Svg
Qt5::Concurrent
Qt5::Multimedia
Qt5::Qml
Qt5::QuickControls2
Qt5::QuickWidgets
nlohmann_json::nlohmann_json
lmdbxx::lmdbxx
liblmdb::lmdb
tweeny
SingleApplication::SingleApplication)
if(MSVC)
target_link_libraries(nheko PRIVATE ntdll)
endif()
if(EXTERNAL_PROJECT_DEPS)
add_dependencies(nheko ${EXTERNAL_PROJECT_DEPS})
if(QML_DEBUGGING)
target_compile_definitions(nheko PRIVATE QML_DEBUGGING)
endif()
if(NOT MSVC)
if("${CMAKE_BUILD_TYPE}" STREQUAL "Debug" OR CI_BUILD)
target_compile_options(nheko PRIVATE "-Werror")
endif()
endif()
set_target_properties(nheko PROPERTIES SKIP_BUILD_RPATH TRUE)
if(UNIX AND NOT APPLE)
install (TARGETS nheko RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")
install (FILES "resources/nheko-16.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/16x16/apps" RENAME "nheko.png")
@ -461,6 +596,7 @@ if(UNIX AND NOT APPLE)
install (FILES "resources/nheko-128.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/128x128/apps" RENAME "nheko.png")
install (FILES "resources/nheko-256.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/256x256/apps" RENAME "nheko.png")
install (FILES "resources/nheko-512.png" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps" RENAME "nheko.png")
install (FILES "resources/nheko.svg" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/scalable/apps" RENAME "nheko.svg")
install (FILES "resources/nheko.desktop" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
install (FILES "resources/nheko.appdata.xml" DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/metainfo")

View file

@ -7,7 +7,7 @@ RUN \
add-apt-repository -y ppa:ubuntu-toolchain-r/test && \
apt-get update -qq && \
apt-get install -y \
qt510base qt510tools qt510svg qt510multimedia \
qt510base qt510tools qt510svg qt510multimedia qt510quickcontrols2 qt510graphicaleffects \
gcc-5 g++-5
RUN \

View file

@ -68,7 +68,7 @@ update-translations:
-locations relative \
-Iinclude/dialogs \
-Iinclude \
src/ -ts resources/langs/nheko_*.ts -no-obsolete
src/ resources/qml/ -ts resources/langs/nheko_*.ts -no-obsolete
clean:
rm -rf build

153
README.md
View file

@ -3,7 +3,7 @@ nheko
[![Build Status](https://travis-ci.org/Nheko-Reborn/nheko.svg?branch=master)](https://travis-ci.org/Nheko-Reborn/nheko)
[![Build status](https://ci.appveyor.com/api/projects/status/07qrqbfylsg4hw2h/branch/master?svg=true)](https://ci.appveyor.com/project/redsky17/nheko/branch/master)
[![Stable Version](https://img.shields.io/badge/download-stable-green.svg)](https://github.com/Nheko-Reborn/nheko/releases/v0.6.4)
[![Nightly](https://img.shields.io/badge/download-nightly-green.svg)](https://bintray.com/nheko-reborn/nheko/nheko)
[![Nightly](https://img.shields.io/badge/download-nightly-green.svg)](https://nheko-reborn-artifacts.s3.us-east-2.amazonaws.com/list.html)
[![#nheko-reborn:matrix.org](https://img.shields.io/matrix/nheko-reborn:matrix.org.svg?label=%23nheko-reborn:matrix.org)](https://matrix.to/#/#nheko-reborn:matrix.org)
[![AUR: nheko](https://img.shields.io/badge/AUR-nheko-blue.svg)](https://aur.archlinux.org/packages/nheko)
<a href='https://flathub.org/apps/details/io.github.NhekoReborn.Nheko'><img width='240' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-en.png'/></a>
@ -11,21 +11,28 @@ nheko
The motivation behind the project is to provide a native desktop app for [Matrix] that
feels more like a mainstream chat app ([Riot], Telegram etc) and less like an IRC client.
### Translations ###
[![Translation status](http://weblate.nheko.im/widgets/nheko/-/nheko-master/svg-badge.svg)](http://weblate.nheko.im/engage/nheko/?utm_source=widget)
Help us with translations so as many people as possible will be able to use nheko!
### Note regarding End-to-End encryption
Currently the implementation is at best a **proof of concept** and it should only be used for
testing purposes.
testing purposes. Most importantly, it is missing device verification, so while your messages
and media are encrypted, nheko doesn't verify who gets the messages.
## Features
Most of the features you would expect from a chat application are missing right now
but we are getting close to a more feature complete client.
Specifically there is support for:
- E2E encryption (text messages only: attachments are currently sent unencrypted).
- E2E encryption.
- User registration.
- Creating, joining & leaving rooms.
- Sending & receiving invites.
- Sending & receiving files and emoji (inline widgets for images, audio and file messages).
- Replies with text, images and other media (and actually render them as inline widgets).
- Typing notifications.
- Username auto-completion.
- Message & mention notifications.
@ -62,7 +69,7 @@ sudo dnf install nheko
#### Gentoo Linux
```bash
sudo layman -a matrix
sudo eselect repository enable matrix
sudo emerge -a nheko
```
@ -86,7 +93,8 @@ flatpak install flathub io.github.NhekoReborn.Nheko
guix install nheko
```
#### macOS (10.12 and above)
#### macOS (10.14 and above)
with [macports](https://www.macports.org/) :
@ -96,21 +104,43 @@ sudo port install nheko
### Build Requirements
- Qt5 (5.7 or greater). Qt 5.7 adds support for color font rendering with
Freetype, which is essential to properly support emoji.
- CMake 3.1 or greater.
- Qt5 (5.10 or greater). Qt 5.7 adds support for color font rendering with
Freetype, which is essential to properly support emoji, 5.8 adds some features
to make interopability with Qml easier, 5.10 makes sliders actually visible with different palettes.
- CMake 3.15 or greater. (Lower version may work, but may break boost linking)
- [mtxclient](https://github.com/Nheko-Reborn/mtxclient)
- [LMDB](https://symas.com/lightning-memory-mapped-database/)
- [cmark](https://github.com/commonmark/cmark)
- Boost 1.66 or greater.
- [cmark](https://github.com/commonmark/cmark) 0.29 or greater.
- Boost 1.70 or greater.
- [libolm](https://git.matrix.org/git/olm)
- [libsodium](https://github.com/jedisct1/libsodium)
- [spdlog](https://github.com/gabime/spdlog)
- A compiler that supports C++ 14:
- Clang 5 (tested on Travis CI)
- A compiler that supports C++ 17:
- Clang 6 (tested on Travis CI)
- GCC 7 (tested on Travis CI)
- MSVC 19.13 (tested on AppVeyor)
Nheko can use bundled version for most of those libraries automatically, if the versions in your distro are too old.
To use them, you can enable the hunter integration by passing `-DHUNTER_ENABLED=ON`.
It is probably wise to link those dependencies statically by passing `-DBUILD_SHARED_LIBS=OFF`
You can select which bundled dependencies you want to use py passing various `-DUSE_BUNDLED_*` flags. By default all dependencies are bundled *if* you enable hunter.
The bundle flags are currently:
- USE_BUNDLED_BOOST
- USE_BUNDLED_SPDLOG
- USE_BUNDLED_OLM
- USE_BUNDLED_GTEST
- USE_BUNDLED_CMARK
- USE_BUNDLED_JSON
- USE_BUNDLED_OPENSSL
- USE_BUNDLED_MTXCLIENT
- USE_BUNDLED_SODIUM
- USE_BUNDLED_ZLIB
- USE_BUNDLED_LMDB
- USE_BUNDLED_LMDBXX
- USE_BUNDLED_TWEENY
#### Linux
If you don't want to install any external dependencies, you can generate an AppImage locally using docker.
@ -138,26 +168,45 @@ sudo pacman -S qt5-base \
##### Gentoo Linux
```bash
sudo emerge -a ">=dev-qt/qtgui-5.7.1" media-libs/fontconfig
sudo emerge -a ">=dev-qt/qtgui-5.9.0" media-libs/fontconfig
```
##### Ubuntu (e.g 14.04)
##### Ubuntu 16.04
```bash
sudo add-apt-repository ppa:beineri/opt-qt592-trusty
sudo add-apt-repository ppa:beineri/opt-qt592-xenial
sudo add-apt-repository ppa:george-edison55/cmake-3.x
sudo add-apt-repository ppa:ubuntu-toolchain-r-test
sudo apt-get update
sudo apt-get install -y g++-7 qt59base qt59svg qt59tools qt59multimedia cmake liblmdb-dev libsodium-dev
```
##### Ubuntu 19.10
```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++-7 cmake liblmdb-dev libsodium-dev qt{base,tools,multimedia}5-dev qml-module-qt{gstreamer,multimedia,quick-extras} libqt5svg5-dev qt{script,quickcontrols2-}5-dev
```
##### Debian Buster (or higher probably)
(User report, not sure if all of those are needed)
```bash
sudo apt install cmake gcc make automake liblmdb-dev libsodium-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
```
##### Guix
```bash
guix environment nheko
```
##### macOS (Xcode 8 or later)
##### macOS (Xcode 10.2 or later)
```bash
brew update
@ -170,61 +219,29 @@ brew install qt5 lmdb cmake llvm libsodium spdlog boost cmark
(for the CMake integration) workloads.
2. Download the latest Qt for windows installer and install it somewhere.
Make sure to install the `MSVC 2017 64-bit` toolset for at least Qt 5.9
Make sure to install the `MSVC 2017 64-bit` toolset for at least Qt 5.10
(lower versions does not support VS2017).
3. Install dependencies with `vcpkg`. You can simply clone it into a subfolder
of the root nheko source directory.
```powershell
git clone http:\\github.com\Microsoft\vcpkg
cd vcpkg
.\bootstrap-vcpkg.bat
.\vcpkg install --triplet x64-windows \
boost-asio \
boost-beast \
boost-iostreams \
boost-random \
boost-signals2 \
boost-system \
boost-thread \
cmark \
libsodium \
lmdb \
openssl \
zlib
```
4. Install dependencies not managed by vcpkg. (libolm, libmtxclient, libmatrix_structs)
Inside the project root run the following (replacing the path to vcpkg as necessary).
```bash
cmake -G "Visual Studio 15 2017 Win64" -Hdeps -B.deps
-DCMAKE_TOOLCHAIN_FILE=C:/Users/<your-path>/vcpkg/scripts/buildsystems/vcpkg.cmake
-DUSE_BUNDLED_BOOST=OFF
cmake --build .deps --config Release
cmake --build .deps --config Debug
```
3. If you don't have openssl installed, you will need to install perl to build it (i.e. Strawberry Perl).
### Building
First we need to install the rest of the dependencies that are not available in our system
We can now build nheko:
```bash
cmake -Hdeps -B.deps \
-DUSE_BUNDLED_BOOST=OFF # if we already have boost & spdlog installed.
-DUSE_BUNDLED_SPDLOG=OFF
cmake --build .deps
```
We can now build nheko by pointing it to the path that we installed the dependencies.
```bash
cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=.deps/usr
cmake -H. -Bbuild -DCMAKE_BUILD_TYPE=Release
cmake --build build
```
To use bundled dependencies you can use hunter, i.e.:
```bash
cmake -H. -Bbuild -DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF -DUSE_BUNDLED_OPENSSL=OFF
cmake --build build --config Release
```
Adapt the USE_BUNDLED_* as needed.
If the build fails with the following error
```
Could not find a package configuration file provided by "Qt5Widgets" with
@ -249,13 +266,14 @@ The `nheko` binary will be located in the `build` directory.
After installing all dependencies, you need to edit the `CMakeSettings.json` to
be able to load and compile nheko within Visual Studio.
You need to fill out the paths for the `CMAKE_TOOLCHAIN_FILE` and the `Qt5_DIR`.
The toolchain file should point to the `vcpkg.cmake` and the Qt5 dir to the `lib\cmake\Qt5` dir.
You need to fill out the paths for the `Qt5_DIR`.
The Qt5 dir should point to the `lib\cmake\Qt5` dir.
Examples for the paths are:
- `C:\\vcpkg\\scripts\\buildsystems\\vcpkg.cmake`
- `C:\\Qt\\5.10.1\\msvc2017_64\\lib\\cmake\\Qt5`
You should also enable hunter by setting `HUNTER_ENABLED` to `ON` and `BUILD_SHARED_LIBS` to `OFF`.
Now right click into the root nheko source directory and choose `Open in Visual Studio`.
You can choose the build type Release and Debug in the top toolbar.
After a successful CMake generation you can select the `nheko.exe` as the run target.
@ -273,6 +291,9 @@ windeployqt nheko.exe
The final binary will be located inside `build-vc\Release\Release` for the Release build
and `build-vc\Debug\Debug` for the Debug build.
Also copy the respective cmark.dll to the binary dir from `build/cmark-build/src/Release` (or Debug).
### Contributing
See [CONTRIBUTING](.github/CONTRIBUTING.md)
@ -288,9 +309,7 @@ Here are some screen shots to get a feel for the UI, but things will probably ch
### Third party
- [Emoji One](http://emojione.com)
- [Font Awesome](http://fontawesome.io/)
- [Open Sans](https://fonts.google.com/specimen/Open+Sans)
[Single Application for Qt](https://github.com/itay-grudev/SingleApplication)
[Matrix]:https://matrix.org
[Riot]:https://riot.im

View file

@ -1,6 +1,6 @@
---
version: 0.6.4-{build}
version: 0.7.0-{build}
configuration: Release
image: Visual Studio 2017
@ -10,30 +10,17 @@ environment:
BINTRAY_APIKEY:
secure: "iGl5mzE9/ta9kFELUxDw9XtlYMSCMai9xowXIkYzU8WKHz7NfW0mLwMJZvblZFXJ"
cache: c:\tools\vcpkg\installed\
cache:
- c:\hunter\ -> appveyor.yml
- build\_deps -> appveyor.yml,deps\CMakeLists.txt
build:
verbosity: minimal
install:
- set QT_DIR=C:\Qt\5.10.1\msvc2017_64
- set PATH=%PATH%;%QT_DIR%\bin;C:\MinGW\bin
- set PATH=%PATH%;C:\mingw-w64\x86_64-7.2.0-posix-seh-rt_v5-rev1\mingw64\bin
- set QT_DIR=C:\Qt\5.13\msvc2017_64
- set PATH=%PATH%;%QT_DIR%\bin
- call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat"
- cd "C:\Tools\vcpkg"&& git pull && .\bootstrap-vcpkg.bat && cd %APPVEYOR_BUILD_FOLDER%
- vcpkg install
nlohmann-json:%PLATFORM%-windows
boost-asio:%PLATFORM%-windows
boost-beast:%PLATFORM%-windows
boost-iostreams:%PLATFORM%-windows
boost-random:%PLATFORM%-windows
boost-signals2:%PLATFORM%-windows
boost-system:%PLATFORM%-windows
boost-thread:%PLATFORM%-windows
libsodium:%PLATFORM%-windows
lmdb:%PLATFORM%-windows
openssl:%PLATFORM%-windows
zlib:%PLATFORM%-windows
build_script:
# VERSION format: branch-master/branch-1.2
@ -54,23 +41,13 @@ build_script:
- echo %INSTVERSION%
- echo %DATE%
# Build & install the dependencies
- cmake -G "Visual Studio 15 2017 Win64" -Hdeps -B.deps
-DCMAKE_TOOLCHAIN_FILE=C:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake
-DUSE_BUNDLED_BOOST=OFF
-DUSE_BUNDLED_JSON=OFF
-DMTX_STATIC=ON
- cmake --build .deps --config Release
# Build nheko
- rm -f cmake/FindOlm.cmake
#- cmake -G "Visual Studio 16 2019" -A x64 -H. -Bbuild
- cmake -G "Visual Studio 15 2017 Win64" -H. -Bbuild
-DCMAKE_TOOLCHAIN_FILE=C:/Tools/vcpkg/scripts/buildsystems/vcpkg.cmake
-DLMDBXX_INCLUDE_DIR=.deps/usr/include
-DTWEENY_INCLUDE_DIR=.deps/usr/include
-DCMARK_INCLUDE_DIR=C:/projects/nheko/.deps/usr/include
-DCMARK_LIBRARY=C:/projects/nheko/.deps/usr/lib/cmark.lib
-DJSON_INCLUDE_DIR=.deps/usr/include
-DHUNTER_ROOT="C:\hunter"
-DHUNTER_ENABLED=ON -DBUILD_SHARED_LIBS=OFF
-DCMAKE_BUILD_TYPE=Release -DHUNTER_CONFIGURATION_TYPES=Release
- cmake --build build --config Release
after_build:
@ -79,14 +56,9 @@ after_build:
- echo %BUILD%
- mkdir NhekoRelease
- copy build\Release\nheko.exe NhekoRelease\nheko.exe
- copy build\_deps\cmark-build\src\Release\cmark.dll NhekoRelease\cmark.dll
- windeployqt --qmldir %QT_DIR%\qml\ --release NhekoRelease\nheko.exe
- copy C:\Tools\vcpkg\installed\x64-windows\lib\*.lib .\NhekoRelease\
- copy C:\Tools\vcpkg\installed\x64-windows\bin\*.dll .\NhekoRelease\
- copy C:\projects\nheko\.deps\usr\lib\cmark.lib .\NhekoRelease\
- copy C:\projects\nheko\.deps\usr\bin\cmark.dll .\NhekoRelease\
- 7z a nheko_win_64.zip .\NhekoRelease\*
- ls -lh build\Release\
- ls -lh NhekoRelease\
@ -135,7 +107,7 @@ on_success:
- if "%APPVEYOR_REPO_TAG%" == "true" (curl -T nheko-%APPVEYOR_REPO_TAG_NAME%-installer.exe -uredsky17:%BINTRAY_APIKEY% https://api.bintray.com/content/nheko-reborn/nheko/%APPVEYOR_REPO_TAG_NAME%/nheko/%APPVEYOR_REPO_TAG_NAME%/)
deploy:
description: "Development builds"
- description: "Development builds"
provider: GitHub
auth_token:
secure: "ShStWeqp+TkYqJPQr7uFZb+B8ZTgC7Iwth+IkhjfRDCTLhy8gtWvlPzlQilder3E"
@ -144,6 +116,13 @@ deploy:
prerelease: true
on:
appveyor_repo_tag: true
- provider: S3
access_key_id: ${AWS_ACCESS_KEY}
secret_access_key: ${AWS_SECRET_KEY}
bucket: ${AWS_BUCKET_NAME}
region: ${AWS_DEFAULT_REGION}
set_public: true
artifact: nheko-$(APPVEYOR_REPO_TAG_NAME)-installer.exe, nheko_win_64.zip
artifacts:
- path: nheko_win_64.zip

15
cmake/FindLMDB.cmake Normal file
View file

@ -0,0 +1,15 @@
#
# Find the lmdb library & include dir.
#
find_path (LMDB_INCLUDE_DIR NAMES lmdb.h PATHS "$ENV{LMDB_DIR}/include")
find_library (LMDB_LIBRARY NAMES lmdb PATHS "$ENV{LMDB_DIR}/lib" )
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LMDB DEFAULT_MSG LMDB_INCLUDE_DIR LMDB_LIBRARY)
add_library(lmdb INTERFACE IMPORTED GLOBAL)
target_include_directories(lmdb INTERFACE ${LMDB_INCLUDE_DIR})
target_link_libraries(lmdb INTERFACE ${LMDB_LIBRARY})
add_library(liblmdb::lmdb ALIAS lmdb)

View file

@ -1,44 +0,0 @@
#
# CMake module to search for the olm library
#
# On success, the macro sets the following variables:
# OLM_FOUND = if the library found
# OLM_LIBRARY = full path to the library
# OLM_INCLUDE_DIR = where to find the library headers
#
if(WIN32)
message(STATUS "FindOlm is not supported in Windows")
return()
endif()
find_path(OLM_INCLUDE_DIR
NAMES olm/olm.h
PATHS /usr/include
/usr/local/include
$ENV{LIB_DIR}/include
$ENV{LIB_DIR}/include/olm)
find_library(OLM_LIBRARY
NAMES olm
PATHS /usr/lib /usr/local/lib $ENV{LIB_DIR}/lib)
if(OLM_FOUND)
set(OLM_INCLUDE_DIRS ${OLM_INCLUDE_DIR})
if(NOT OLM_LIBRARIES)
set(OLM_LIBRARIES ${OLM_LIBRARY})
endif()
endif()
if(NOT TARGET Olm::Olm)
add_library(Olm::Olm UNKNOWN IMPORTED)
set_target_properties(Olm::Olm
PROPERTIES INTERFACE_INCLUDE_DIRECTORIES
${OLM_INCLUDE_DIR})
set_property(TARGET Olm::Olm APPEND PROPERTY IMPORTED_LOCATION ${OLM_LIBRARY})
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(OLM DEFAULT_MSG OLM_INCLUDE_DIR OLM_LIBRARY)
mark_as_advanced(OLM_LIBRARY OLM_INCLUDE_DIR)

View file

@ -0,0 +1,5 @@
hunter_config(
Boost
VERSION "1.70.0-p0"
CMAKE_ARGS IOSTREAMS_NO_BZIP2=1
)

528
cmake/HunterGate.cmake Normal file
View file

@ -0,0 +1,528 @@
# Copyright (c) 2013-2019, Ruslan Baratov
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# This is a gate file to Hunter package manager.
# Include this file using `include` command and add package you need, example:
#
# cmake_minimum_required(VERSION 3.2)
#
# include("cmake/HunterGate.cmake")
# HunterGate(
# URL "https://github.com/path/to/hunter/archive.tar.gz"
# SHA1 "798501e983f14b28b10cda16afa4de69eee1da1d"
# )
#
# project(MyProject)
#
# hunter_add_package(Foo)
# hunter_add_package(Boo COMPONENTS Bar Baz)
#
# Projects:
# * https://github.com/hunter-packages/gate/
# * https://github.com/ruslo/hunter
option(HUNTER_ENABLED "Enable Hunter package manager support" ON)
if(HUNTER_ENABLED)
if(CMAKE_VERSION VERSION_LESS "3.2")
message(
FATAL_ERROR
"At least CMake version 3.2 required for Hunter dependency management."
" Update CMake or set HUNTER_ENABLED to OFF."
)
endif()
endif()
include(CMakeParseArguments) # cmake_parse_arguments
option(HUNTER_STATUS_PRINT "Print working status" ON)
option(HUNTER_STATUS_DEBUG "Print a lot info" OFF)
option(HUNTER_TLS_VERIFY "Enable/disable TLS certificate checking on downloads" ON)
set(HUNTER_ERROR_PAGE "https://docs.hunter.sh/en/latest/reference/errors")
function(hunter_gate_status_print)
if(HUNTER_STATUS_PRINT OR HUNTER_STATUS_DEBUG)
foreach(print_message ${ARGV})
message(STATUS "[hunter] ${print_message}")
endforeach()
endif()
endfunction()
function(hunter_gate_status_debug)
if(HUNTER_STATUS_DEBUG)
foreach(print_message ${ARGV})
string(TIMESTAMP timestamp)
message(STATUS "[hunter *** DEBUG *** ${timestamp}] ${print_message}")
endforeach()
endif()
endfunction()
function(hunter_gate_error_page error_page)
message("------------------------------ ERROR ------------------------------")
message(" ${HUNTER_ERROR_PAGE}/${error_page}.html")
message("-------------------------------------------------------------------")
message("")
message(FATAL_ERROR "")
endfunction()
function(hunter_gate_internal_error)
message("")
foreach(print_message ${ARGV})
message("[hunter ** INTERNAL **] ${print_message}")
endforeach()
message("[hunter ** INTERNAL **] [Directory:${CMAKE_CURRENT_LIST_DIR}]")
message("")
hunter_gate_error_page("error.internal")
endfunction()
function(hunter_gate_fatal_error)
cmake_parse_arguments(hunter "" "ERROR_PAGE" "" "${ARGV}")
if("${hunter_ERROR_PAGE}" STREQUAL "")
hunter_gate_internal_error("Expected ERROR_PAGE")
endif()
message("")
foreach(x ${hunter_UNPARSED_ARGUMENTS})
message("[hunter ** FATAL ERROR **] ${x}")
endforeach()
message("[hunter ** FATAL ERROR **] [Directory:${CMAKE_CURRENT_LIST_DIR}]")
message("")
hunter_gate_error_page("${hunter_ERROR_PAGE}")
endfunction()
function(hunter_gate_user_error)
hunter_gate_fatal_error(${ARGV} ERROR_PAGE "error.incorrect.input.data")
endfunction()
function(hunter_gate_self root version sha1 result)
string(COMPARE EQUAL "${root}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("root is empty")
endif()
string(COMPARE EQUAL "${version}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("version is empty")
endif()
string(COMPARE EQUAL "${sha1}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("sha1 is empty")
endif()
string(SUBSTRING "${sha1}" 0 7 archive_id)
set(
hunter_self
"${root}/_Base/Download/Hunter/${version}/${archive_id}/Unpacked"
)
set("${result}" "${hunter_self}" PARENT_SCOPE)
endfunction()
# Set HUNTER_GATE_ROOT cmake variable to suitable value.
function(hunter_gate_detect_root)
# Check CMake variable
string(COMPARE NOTEQUAL "${HUNTER_ROOT}" "" not_empty)
if(not_empty)
set(HUNTER_GATE_ROOT "${HUNTER_ROOT}" PARENT_SCOPE)
hunter_gate_status_debug("HUNTER_ROOT detected by cmake variable")
return()
endif()
# Check environment variable
string(COMPARE NOTEQUAL "$ENV{HUNTER_ROOT}" "" not_empty)
if(not_empty)
set(HUNTER_GATE_ROOT "$ENV{HUNTER_ROOT}" PARENT_SCOPE)
hunter_gate_status_debug("HUNTER_ROOT detected by environment variable")
return()
endif()
# Check HOME environment variable
string(COMPARE NOTEQUAL "$ENV{HOME}" "" result)
if(result)
set(HUNTER_GATE_ROOT "$ENV{HOME}/.hunter" PARENT_SCOPE)
hunter_gate_status_debug("HUNTER_ROOT set using HOME environment variable")
return()
endif()
# Check SYSTEMDRIVE and USERPROFILE environment variable (windows only)
if(WIN32)
string(COMPARE NOTEQUAL "$ENV{SYSTEMDRIVE}" "" result)
if(result)
set(HUNTER_GATE_ROOT "$ENV{SYSTEMDRIVE}/.hunter" PARENT_SCOPE)
hunter_gate_status_debug(
"HUNTER_ROOT set using SYSTEMDRIVE environment variable"
)
return()
endif()
string(COMPARE NOTEQUAL "$ENV{USERPROFILE}" "" result)
if(result)
set(HUNTER_GATE_ROOT "$ENV{USERPROFILE}/.hunter" PARENT_SCOPE)
hunter_gate_status_debug(
"HUNTER_ROOT set using USERPROFILE environment variable"
)
return()
endif()
endif()
hunter_gate_fatal_error(
"Can't detect HUNTER_ROOT"
ERROR_PAGE "error.detect.hunter.root"
)
endfunction()
function(hunter_gate_download dir)
string(
COMPARE
NOTEQUAL
"$ENV{HUNTER_DISABLE_AUTOINSTALL}"
""
disable_autoinstall
)
if(disable_autoinstall AND NOT HUNTER_RUN_INSTALL)
hunter_gate_fatal_error(
"Hunter not found in '${dir}'"
"Set HUNTER_RUN_INSTALL=ON to auto-install it from '${HUNTER_GATE_URL}'"
"Settings:"
" HUNTER_ROOT: ${HUNTER_GATE_ROOT}"
" HUNTER_SHA1: ${HUNTER_GATE_SHA1}"
ERROR_PAGE "error.run.install"
)
endif()
string(COMPARE EQUAL "${dir}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("Empty 'dir' argument")
endif()
string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("HUNTER_GATE_SHA1 empty")
endif()
string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" is_bad)
if(is_bad)
hunter_gate_internal_error("HUNTER_GATE_URL empty")
endif()
set(done_location "${dir}/DONE")
set(sha1_location "${dir}/SHA1")
set(build_dir "${dir}/Build")
set(cmakelists "${dir}/CMakeLists.txt")
hunter_gate_status_debug("Locking directory: ${dir}")
file(LOCK "${dir}" DIRECTORY GUARD FUNCTION)
hunter_gate_status_debug("Lock done")
if(EXISTS "${done_location}")
# while waiting for lock other instance can do all the job
hunter_gate_status_debug("File '${done_location}' found, skip install")
return()
endif()
file(REMOVE_RECURSE "${build_dir}")
file(REMOVE_RECURSE "${cmakelists}")
file(MAKE_DIRECTORY "${build_dir}") # check directory permissions
# Disabling languages speeds up a little bit, reduces noise in the output
# and avoids path too long windows error
file(
WRITE
"${cmakelists}"
"cmake_minimum_required(VERSION 3.2)\n"
"project(HunterDownload LANGUAGES NONE)\n"
"include(ExternalProject)\n"
"ExternalProject_Add(\n"
" Hunter\n"
" URL\n"
" \"${HUNTER_GATE_URL}\"\n"
" URL_HASH\n"
" SHA1=${HUNTER_GATE_SHA1}\n"
" DOWNLOAD_DIR\n"
" \"${dir}\"\n"
" TLS_VERIFY\n"
" ${HUNTER_TLS_VERIFY}\n"
" SOURCE_DIR\n"
" \"${dir}/Unpacked\"\n"
" CONFIGURE_COMMAND\n"
" \"\"\n"
" BUILD_COMMAND\n"
" \"\"\n"
" INSTALL_COMMAND\n"
" \"\"\n"
")\n"
)
if(HUNTER_STATUS_DEBUG)
set(logging_params "")
else()
set(logging_params OUTPUT_QUIET)
endif()
hunter_gate_status_debug("Run generate")
# Need to add toolchain file too.
# Otherwise on Visual Studio + MDD this will fail with error:
# "Could not find an appropriate version of the Windows 10 SDK installed on this machine"
if(EXISTS "${CMAKE_TOOLCHAIN_FILE}")
get_filename_component(absolute_CMAKE_TOOLCHAIN_FILE "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE)
set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=${absolute_CMAKE_TOOLCHAIN_FILE}")
else()
# 'toolchain_arg' can't be empty
set(toolchain_arg "-DCMAKE_TOOLCHAIN_FILE=")
endif()
string(COMPARE EQUAL "${CMAKE_MAKE_PROGRAM}" "" no_make)
if(no_make)
set(make_arg "")
else()
# Test case: remove Ninja from PATH but set it via CMAKE_MAKE_PROGRAM
set(make_arg "-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}")
endif()
execute_process(
COMMAND
"${CMAKE_COMMAND}"
"-H${dir}"
"-B${build_dir}"
"-G${CMAKE_GENERATOR}"
"${toolchain_arg}"
${make_arg}
WORKING_DIRECTORY "${dir}"
RESULT_VARIABLE download_result
${logging_params}
)
if(NOT download_result EQUAL 0)
hunter_gate_internal_error(
"Configure project failed."
"To reproduce the error run: ${CMAKE_COMMAND} -H${dir} -B${build_dir} -G${CMAKE_GENERATOR} ${toolchain_arg} ${make_arg}"
"In directory ${dir}"
)
endif()
hunter_gate_status_print(
"Initializing Hunter workspace (${HUNTER_GATE_SHA1})"
" ${HUNTER_GATE_URL}"
" -> ${dir}"
)
execute_process(
COMMAND "${CMAKE_COMMAND}" --build "${build_dir}"
WORKING_DIRECTORY "${dir}"
RESULT_VARIABLE download_result
${logging_params}
)
if(NOT download_result EQUAL 0)
hunter_gate_internal_error("Build project failed")
endif()
file(REMOVE_RECURSE "${build_dir}")
file(REMOVE_RECURSE "${cmakelists}")
file(WRITE "${sha1_location}" "${HUNTER_GATE_SHA1}")
file(WRITE "${done_location}" "DONE")
hunter_gate_status_debug("Finished")
endfunction()
# Must be a macro so master file 'cmake/Hunter' can
# apply all variables easily just by 'include' command
# (otherwise PARENT_SCOPE magic needed)
macro(HunterGate)
if(HUNTER_GATE_DONE)
# variable HUNTER_GATE_DONE set explicitly for external project
# (see `hunter_download`)
set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES)
endif()
# First HunterGate command will init Hunter, others will be ignored
get_property(_hunter_gate_done GLOBAL PROPERTY HUNTER_GATE_DONE SET)
if(NOT HUNTER_ENABLED)
# Empty function to avoid error "unknown function"
function(hunter_add_package)
endfunction()
set(
_hunter_gate_disabled_mode_dir
"${CMAKE_CURRENT_LIST_DIR}/cmake/Hunter/disabled-mode"
)
if(EXISTS "${_hunter_gate_disabled_mode_dir}")
hunter_gate_status_debug(
"Adding \"disabled-mode\" modules: ${_hunter_gate_disabled_mode_dir}"
)
list(APPEND CMAKE_PREFIX_PATH "${_hunter_gate_disabled_mode_dir}")
endif()
elseif(_hunter_gate_done)
hunter_gate_status_debug("Secondary HunterGate (use old settings)")
hunter_gate_self(
"${HUNTER_CACHED_ROOT}"
"${HUNTER_VERSION}"
"${HUNTER_SHA1}"
_hunter_self
)
include("${_hunter_self}/cmake/Hunter")
else()
set(HUNTER_GATE_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}")
string(COMPARE NOTEQUAL "${PROJECT_NAME}" "" _have_project_name)
if(_have_project_name)
hunter_gate_fatal_error(
"Please set HunterGate *before* 'project' command. "
"Detected project: ${PROJECT_NAME}"
ERROR_PAGE "error.huntergate.before.project"
)
endif()
cmake_parse_arguments(
HUNTER_GATE "LOCAL" "URL;SHA1;GLOBAL;FILEPATH" "" ${ARGV}
)
string(COMPARE EQUAL "${HUNTER_GATE_SHA1}" "" _empty_sha1)
string(COMPARE EQUAL "${HUNTER_GATE_URL}" "" _empty_url)
string(
COMPARE
NOTEQUAL
"${HUNTER_GATE_UNPARSED_ARGUMENTS}"
""
_have_unparsed
)
string(COMPARE NOTEQUAL "${HUNTER_GATE_GLOBAL}" "" _have_global)
string(COMPARE NOTEQUAL "${HUNTER_GATE_FILEPATH}" "" _have_filepath)
if(_have_unparsed)
hunter_gate_user_error(
"HunterGate unparsed arguments: ${HUNTER_GATE_UNPARSED_ARGUMENTS}"
)
endif()
if(_empty_sha1)
hunter_gate_user_error("SHA1 suboption of HunterGate is mandatory")
endif()
if(_empty_url)
hunter_gate_user_error("URL suboption of HunterGate is mandatory")
endif()
if(_have_global)
if(HUNTER_GATE_LOCAL)
hunter_gate_user_error("Unexpected LOCAL (already has GLOBAL)")
endif()
if(_have_filepath)
hunter_gate_user_error("Unexpected FILEPATH (already has GLOBAL)")
endif()
endif()
if(HUNTER_GATE_LOCAL)
if(_have_global)
hunter_gate_user_error("Unexpected GLOBAL (already has LOCAL)")
endif()
if(_have_filepath)
hunter_gate_user_error("Unexpected FILEPATH (already has LOCAL)")
endif()
endif()
if(_have_filepath)
if(_have_global)
hunter_gate_user_error("Unexpected GLOBAL (already has FILEPATH)")
endif()
if(HUNTER_GATE_LOCAL)
hunter_gate_user_error("Unexpected LOCAL (already has FILEPATH)")
endif()
endif()
hunter_gate_detect_root() # set HUNTER_GATE_ROOT
# Beautify path, fix probable problems with windows path slashes
get_filename_component(
HUNTER_GATE_ROOT "${HUNTER_GATE_ROOT}" ABSOLUTE
)
hunter_gate_status_debug("HUNTER_ROOT: ${HUNTER_GATE_ROOT}")
if(NOT HUNTER_ALLOW_SPACES_IN_PATH)
string(FIND "${HUNTER_GATE_ROOT}" " " _contain_spaces)
if(NOT _contain_spaces EQUAL -1)
hunter_gate_fatal_error(
"HUNTER_ROOT (${HUNTER_GATE_ROOT}) contains spaces."
"Set HUNTER_ALLOW_SPACES_IN_PATH=ON to skip this error"
"(Use at your own risk!)"
ERROR_PAGE "error.spaces.in.hunter.root"
)
endif()
endif()
string(
REGEX
MATCH
"[0-9]+\\.[0-9]+\\.[0-9]+[-_a-z0-9]*"
HUNTER_GATE_VERSION
"${HUNTER_GATE_URL}"
)
string(COMPARE EQUAL "${HUNTER_GATE_VERSION}" "" _is_empty)
if(_is_empty)
set(HUNTER_GATE_VERSION "unknown")
endif()
hunter_gate_self(
"${HUNTER_GATE_ROOT}"
"${HUNTER_GATE_VERSION}"
"${HUNTER_GATE_SHA1}"
_hunter_self
)
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")
# 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 "${_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()
include("${_master_location}")
set_property(GLOBAL PROPERTY HUNTER_GATE_DONE YES)
endif()
endmacro()

View file

@ -1,29 +0,0 @@
#
# Find the lmdb library & include dir.
# Build lmdb on Appveyor.
#
if(APPVEYOR_BUILD)
set(LMDB_VERSION "LMDB_0.9.21")
set(NTDLIB "C:/WINDDK/7600.16385.1/lib/win7/amd64/ntdll.lib")
execute_process(
COMMAND git clone --depth=1 --branch ${LMDB_VERSION} https://github.com/LMDB/lmdb)
set(LMDB_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/lmdb/libraries/liblmdb)
add_library(lmdb
${CMAKE_SOURCE_DIR}/lmdb/libraries/liblmdb/lmdb.h
${CMAKE_SOURCE_DIR}/lmdb/libraries/liblmdb/mdb.c
${CMAKE_SOURCE_DIR}/lmdb/libraries/liblmdb/midl.h
${CMAKE_SOURCE_DIR}/lmdb/libraries/liblmdb/midl.c)
set(LMDB_LIBRARY lmdb)
else()
find_path (LMDB_INCLUDE_DIR NAMES lmdb.h PATHS "$ENV{LMDB_DIR}/include")
find_library (LMDB_LIBRARY NAMES lmdb PATHS "$ENV{LMDB_DIR}/lib" )
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LMDB DEFAULT_MSG LMDB_INCLUDE_DIR LMDB_LIBRARY)
endif()
include_directories(${LMDB_INCLUDE_DIR})

View file

@ -21,4 +21,8 @@ if(NOT EXISTS ${_qrc})
endif()
qt5_add_resources(LANG_QRC ${_qrc})
if(Qt5QuickCompiler_FOUND)
qtquick_compiler_add_resources(QRC resources/res.qrc)
else()
qt5_add_resources(QRC resources/res.qrc)
endif()

125
deps/CMakeLists.txt vendored
View file

@ -1,125 +0,0 @@
cmake_minimum_required(VERSION 3.11)
project(NHEKO_DEPS)
# Point CMake at any custom modules we may ship
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake")
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
set(DEPS_INSTALL_DIR "${CMAKE_BINARY_DIR}/usr"
CACHE PATH "Dependencies install directory.")
set(DEPS_BIN_DIR "${DEPS_INSTALL_DIR}/bin"
CACHE PATH "Dependencies binary install directory.")
set(DEPS_LIB_DIR "${DEPS_INSTALL_DIR}/lib"
CACHE PATH "Dependencies library install directory.")
set(DEPS_BUILD_DIR "${CMAKE_BINARY_DIR}/build"
CACHE PATH "Dependencies build directory.")
set(DEPS_DOWNLOAD_DIR "${DEPS_BUILD_DIR}/downloads"
CACHE PATH "Dependencies download directory.")
option(USE_BUNDLED "Use bundled dependencies." ON)
option(USE_BUNDLED_BOOST "Use the bundled version of Boost." ${USE_BUNDLED})
option(USE_BUNDLED_CMARK "Use the bundled version of cmark." ${USE_BUNDLED})
option(USE_BUNDLED_SPDLOG "Use the bundled version of spdlog." ${USE_BUNDLED})
option(USE_BUNDLED_OLM "Use the bundled version of libolm." ${USE_BUNDLED})
option(USE_BUNDLED_TWEENY "Use the bundled version of Tweeny." ${USE_BUNDLED})
option(USE_BUNDLED_LMDBXX "Use the bundled version of lmdbxx." ${USE_BUNDLED})
option(USE_BUNDLED_MATRIX_CLIENT "Use the bundled version of mtxclient."
${USE_BUNDLED})
option(USE_BUNDLED_JSON "Use the bundled version of nlohmann json." ${USE_BUNDLED})
option(MTX_STATIC "Compile / link bundled mtx client statically" OFF)
if(USE_BUNDLED_BOOST)
# bundled boost is 1.68, which requires CMake 3.12 or greater.
cmake_minimum_required(VERSION 3.12)
endif()
include(ExternalProject)
set(BOOST_URL
https://dl.bintray.com/boostorg/release/1.69.0/source/boost_1_69_0.tar.bz2)
set(BOOST_SHA256
8f32d4617390d1c2d16f26a27ab60d97807b35440d45891fa340fc2648b04406)
set(
MTXCLIENT_URL
https://github.com/Nheko-Reborn/mtxclient/archive/975ce8906c42742dbb698fcf9fa15663c530df20.tar.gz)
set(MTXCLIENT_HASH
5e3169ef19b6e585069ceced42489574ce18380480628339bac015759fa1893e)
set(
TWEENY_URL
https://github.com/mobius3/tweeny/archive/b94ce07cfb02a0eb8ac8aaf66137dabdaea857cf.tar.gz
)
set(TWEENY_HASH
9a632b9da84823fae002ad5d9ba02c8d77c0a3810479974c6b637c5504165475)
set(
LMDBXX_HEADER_URL
https://raw.githubusercontent.com/bendiken/lmdbxx/0b43ca87d8cfabba392dfe884eb1edb83874de02/lmdb%2B%2B.h
)
set(LMDBXX_HASH
c57b501a4e8fa1187fa7fd348da415c7685a50a7cb25b17b3f257b9e9426f73d)
set(OLM_URL https://gitlab.matrix.org/matrix-org/olm.git)
set(OLM_TAG 4065c8e11a33ba41133a086ed3de4da94dcb6bae)
set(CMARK_URL https://github.com/commonmark/cmark/archive/0.28.3.tar.gz)
set(CMARK_HASH acc98685d3c1b515ff787ac7c994188dadaf28a2d700c10c1221da4199bae1fc)
set(SPDLOG_URL https://github.com/gabime/spdlog/archive/v1.1.0.tar.gz)
set(SPDLOG_HASH
3dbcbfd8c07e25f5e0d662b194d3a7772ef214358c49ada23c044c4747ce8b19)
set(JSON_URL
https://github.com/nlohmann/json.git)
set(JSON_TAG
v3.2.0)
if(USE_BUNDLED_JSON)
include(Json)
endif()
if(USE_BUNDLED_BOOST)
include(Boost)
endif()
if(USE_BUNDLED_SPDLOG)
include(SpdLog)
endif()
if(USE_BUNDLED_OLM)
include(Olm)
endif()
if(USE_BUNDLED_CMARK)
include(cmark)
endif()
if(USE_BUNDLED_TWEENY)
include(Tweeny)
endif()
if(USE_BUNDLED_LMDBXX)
file(DOWNLOAD ${LMDBXX_HEADER_URL} ${DEPS_INSTALL_DIR}/include/lmdb++.h
EXPECTED_HASH SHA256=${LMDBXX_HASH})
endif()
if(WIN32)
if("${TARGET_ARCH}" STREQUAL "X86_64")
set(TARGET_ARCH x64)
elseif(TARGET_ARCH STREQUAL "X86")
set(TARGET_ARCH ia32)
endif()
endif()
add_custom_target(third-party ALL
COMMAND ${CMAKE_COMMAND} -E touch .third-party
DEPENDS ${THIRD_PARTY_DEPS})
if(USE_BUNDLED_MATRIX_CLIENT)
include(MatrixClient)
add_dependencies(MatrixClient third-party)
endif()

View file

@ -1,23 +0,0 @@
if(WIN32)
message(STATUS "Building Boost in Windows is not supported (skipping)")
return()
endif()
ExternalProject_Add(
Boost
URL ${BOOST_URL}
URL_HASH SHA256=${BOOST_SHA256}
DOWNLOAD_DIR ${DEPS_DOWNLOAD_DIR}/boost
DOWNLOAD_NO_PROGRESS 0
BUILD_IN_SOURCE 1
SOURCE_DIR ${DEPS_BUILD_DIR}/boost
CONFIGURE_COMMAND ${DEPS_BUILD_DIR}/boost/bootstrap.sh
--with-libraries=random,thread,system,iostreams,atomic,chrono,date_time,regex
--prefix=${DEPS_INSTALL_DIR}
BUILD_COMMAND ${DEPS_BUILD_DIR}/boost/b2 -d0 cxxstd=14 variant=release link=shared runtime-link=shared threading=multi --layout=system
INSTALL_COMMAND ${DEPS_BUILD_DIR}/boost/b2 -d0 install
)
list(APPEND THIRD_PARTY_DEPS Boost)

19
deps/cmake/Json.cmake vendored
View file

@ -1,19 +0,0 @@
ExternalProject_Add(
Json
GIT_REPOSITORY ${JSON_URL}
GIT_TAG ${JSON_TAG}
BUILD_IN_SOURCE 1
SOURCE_DIR ${DEPS_BUILD_DIR}/json
CONFIGURE_COMMAND ${CMAKE_COMMAND}
-DJSON_BuildTests=OFF
-DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR}
-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}
BUILD_COMMAND ${CMAKE_COMMAND} --build ${DEPS_BUILD_DIR}/json
INSTALL_COMMAND make install
)
list(APPEND THIRD_PARTY_DEPS Json)

View file

@ -1,43 +0,0 @@
set(PLATFORM_FLAGS "")
if(MSVC)
set(PLATFORM_FLAGS "-DCMAKE_GENERATOR_PLATFORM=x64")
endif()
if(APPLE)
set(PLATFORM_FLAGS "-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl")
endif()
# Force to build with the bundled version of Boost. This is necessary because
# if an outdated version of Boost is installed, then CMake will grab that
# instead of the bundled version of Boost, like we wanted.
set(BOOST_BUNDLE_ROOT "-DBOOST_ROOT=${DEPS_BUILD_DIR}/boost")
set (MTX_SHARED ON)
if (MTX_STATIC)
set (MTX_SHARED OFF)
endif()
ExternalProject_Add(
MatrixClient
URL ${MTXCLIENT_URL}
URL_HASH SHA256=${MTXCLIENT_HASH}
BUILD_IN_SOURCE 1
SOURCE_DIR ${DEPS_BUILD_DIR}/mtxclient
CONFIGURE_COMMAND ${CMAKE_COMMAND}
-DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR}
-DCMAKE_BUILD_TYPE=Release
-DBUILD_LIB_TESTS=OFF
-DBUILD_LIB_EXAMPLES=OFF
-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}
${BOOST_BUNDLE_ROOT}
-DBUILD_SHARED_LIBS=${MTX_SHARED}
${PLATFORM_FLAGS}
${DEPS_BUILD_DIR}/mtxclient
BUILD_COMMAND
${CMAKE_COMMAND} --build ${DEPS_BUILD_DIR}/mtxclient --config Release)
list(APPEND THIRD_PARTY_DEPS MatrixClient)

34
deps/cmake/Olm.cmake vendored
View file

@ -1,34 +0,0 @@
set(WINDOWS_FLAGS "")
if(MSVC)
set(WINDOWS_FLAGS "-DCMAKE_GENERATOR_PLATFORM=x64")
endif()
ExternalProject_Add(
Olm
GIT_REPOSITORY ${OLM_URL}
GIT_TAG ${OLM_TAG}
BUILD_IN_SOURCE 1
SOURCE_DIR ${DEPS_BUILD_DIR}/olm
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/cmake/OlmCMakeLists.txt
${DEPS_BUILD_DIR}/olm/CMakeLists.txt
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/cmake/OlmConfig.cmake.in
${DEPS_BUILD_DIR}/olm/cmake/OlmConfig.cmake.in
COMMAND ${CMAKE_COMMAND}
-DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR}
-DCMAKE_BUILD_TYPE=Release
${DEPS_BUILD_DIR}/olm
${WINDOWS_FLAGS}
BUILD_COMMAND ${CMAKE_COMMAND}
--build ${DEPS_BUILD_DIR}/olm
--config Release
INSTALL_COMMAND ${CMAKE_COMMAND}
--build ${DEPS_BUILD_DIR}/olm
--config Release
--target install)
list(APPEND THIRD_PARTY_DEPS Olm)

View file

@ -1,107 +0,0 @@
cmake_minimum_required(VERSION 3.1)
project(olm VERSION 2.2.2 LANGUAGES CXX C)
add_definitions(-DOLMLIB_VERSION_MAJOR=${PROJECT_VERSION_MAJOR})
add_definitions(-DOLMLIB_VERSION_MINOR=${PROJECT_VERSION_MINOR})
add_definitions(-DOLMLIB_VERSION_PATCH=${PROJECT_VERSION_PATCH})
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
add_library(olm
src/account.cpp
src/base64.cpp
src/cipher.cpp
src/crypto.cpp
src/memory.cpp
src/message.cpp
src/pickle.cpp
src/ratchet.cpp
src/session.cpp
src/utility.cpp
src/ed25519.c
src/error.c
src/inbound_group_session.c
src/megolm.c
src/olm.cpp
src/outbound_group_session.c
src/pickle_encoding.c
lib/crypto-algorithms/aes.c
lib/crypto-algorithms/sha256.c
lib/curve25519-donna/curve25519-donna.c)
add_library(Olm::Olm ALIAS olm)
target_include_directories(olm
PUBLIC
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/lib)
set_target_properties(olm PROPERTIES
SOVERSION ${PROJECT_VERSION_MAJOR}
VERSION ${PROJECT_VERSION})
set_target_properties(olm PROPERTIES
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR})
#
# Installation
#
include(GNUInstallDirs)
set(INSTALL_CONFIGDIR ${CMAKE_INSTALL_LIBDIR}/cmake/Olm)
install(TARGETS olm
EXPORT olm-targets
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
# The exported target will be named Olm.
set_target_properties(olm PROPERTIES EXPORT_NAME Olm)
install(FILES
${CMAKE_SOURCE_DIR}/include/olm/olm.h
${CMAKE_SOURCE_DIR}/include/olm/outbound_group_session.h
${CMAKE_SOURCE_DIR}/include/olm/inbound_group_session.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/olm)
# Export the targets to a script.
install(EXPORT olm-targets
FILE OlmTargets.cmake
NAMESPACE Olm::
DESTINATION ${INSTALL_CONFIGDIR})
# Create a ConfigVersion.cmake file.
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
${CMAKE_CURRENT_BINARY_DIR}/OlmConfigVersion.cmake
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion)
configure_package_config_file(
${CMAKE_CURRENT_LIST_DIR}/cmake/OlmConfig.cmake.in
${CMAKE_CURRENT_BINARY_DIR}/OlmConfig.cmake
INSTALL_DESTINATION ${INSTALL_CONFIGDIR})
#Install the config & configversion.
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/OlmConfig.cmake
${CMAKE_CURRENT_BINARY_DIR}/OlmConfigVersion.cmake
DESTINATION ${INSTALL_CONFIGDIR})
# Register package in user's package registry
export(EXPORT olm-targets
FILE ${CMAKE_CURRENT_BINARY_DIR}/OlmTargets.cmake
NAMESPACE Olm::)
export(PACKAGE Olm)

View file

@ -1,11 +0,0 @@
get_filename_component(Olm_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH)
include(CMakeFindDependencyMacro)
list(APPEND CMAKE_MODULE_PATH ${Olm_CMAKE_DIR})
list(REMOVE_AT CMAKE_MODULE_PATH -1)
if(NOT TARGET Olm::Olm)
include("${Olm_CMAKE_DIR}/OlmTargets.cmake")
endif()
set(Olm_LIBRARIES Olm::Olm)

View file

@ -1,23 +0,0 @@
set(WINDOWS_FLAGS "")
if(MSVC)
set(WINDOWS_FLAGS "-DCMAKE_GENERATOR_PLATFORM=x64")
endif()
ExternalProject_Add(
SpdLog
URL ${SPDLOG_URL}
URL_HASH SHA256=${SPDLOG_HASH}
BUILD_IN_SOURCE 1
SOURCE_DIR ${DEPS_BUILD_DIR}/spdlog
CONFIGURE_COMMAND ${CMAKE_COMMAND}
-DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR}
-DSPDLOG_BUILD_EXAMPLES=0
-DSPDLOG_BUILD_BENCH=0
-DSPDLOG_BUILD_TESTING=0
${DEPS_BUILD_DIR}/spdlog
${WINDOWS_FLAGS})
list(APPEND THIRD_PARTY_DEPS SpdLog)

View file

@ -1,22 +0,0 @@
set(WINDOWS_FLAGS "")
if(MSVC)
set(WINDOWS_FLAGS "-DCMAKE_GENERATOR_PLATFORM=x64")
endif()
ExternalProject_Add(
Tweeny
URL ${TWEENY_URL}
URL_HASH SHA256=${TWEENY_HASH}
BUILD_IN_SOURCE 1
SOURCE_DIR ${DEPS_BUILD_DIR}/tweeny
CONFIGURE_COMMAND ${CMAKE_COMMAND}
-DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR}
-DTWEENY_BUILD_EXAMPLES=OFF
-DTWEENY_BUILD_DOCUMENTATION=OFF
${DEPS_BUILD_DIR}/tweeny
${WINDOWS_FLAGS})
list(APPEND THIRD_PARTY_DEPS Tweeny)

View file

@ -1,21 +0,0 @@
set(WINDOWS_FLAGS "")
if(MSVC)
set(WINDOWS_FLAGS "-DCMAKE_GENERATOR_PLATFORM=x64")
endif()
ExternalProject_Add(
cmark
URL ${CMARK_URL}
URL_HASH SHA256=${CMARK_HASH}
BUILD_IN_SOURCE 0
SOURCE_DIR ${DEPS_BUILD_DIR}/cmark
CONFIGURE_COMMAND ${CMAKE_COMMAND}
-DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL_DIR}
-DCMARK_TESTS=OFF
${DEPS_BUILD_DIR}/cmark
${WINDOWS_FLAGS})
list(APPEND THIRD_PARTY_DEPS cmark)

View file

@ -0,0 +1,194 @@
{
"id": "io.github.NhekoReborn.Nheko",
"command": "nheko",
"branch": "0.7.0-dev",
"runtime": "org.kde.Platform",
"runtime-version": "5.14",
"sdk": "org.kde.Sdk",
"rename-icon": "nheko",
"rename-desktop-file": "nheko.desktop",
"rename-appdata-file": "nheko.appdata.xml",
"finish-args": [
"--device=dri",
"--filesystem=home",
"--share=ipc",
"--share=network",
"--socket=pulseaudio",
"--socket=wayland",
"--socket=x11",
"--talk-name=org.freedesktop.Notifications",
"--talk-name=org.kde.StatusNotifierWatcher"
],
"cleanup": [
"/include",
"/bin/mdb*",
"*.a"
],
"build-options" : {
"arch": {
"aarch64": {
"cxxflags": "-DBOOST_ASIO_DISABLE_EPOLL"
}
}
},
"modules": [
{
"name": "lmdb",
"sources": [
{
"sha256": "f3927859882eb608868c8c31586bb7eb84562a40a6bf5cc3e13b6b564641ea28",
"type": "archive",
"url": "https://github.com/LMDB/lmdb/archive/LMDB_0.9.22.tar.gz"
}
],
"make-install-args": [
"prefix=/app"
],
"no-autogen": true,
"subdir": "libraries/liblmdb"
},
{
"name": "cmark",
"buildsystem": "cmake-ninja",
"builddir": true,
"config-opts": [
"-DCMAKE_BUILD_TYPE=Release",
"-DCMARK_TESTS=OFF"
],
"sources": [
{
"sha256": "2558ace3cbeff85610de3bda32858f722b359acdadf0c4691851865bb84924a6",
"type": "archive",
"url": "https://github.com/commonmark/cmark/archive/0.29.0.tar.gz"
}
]
},
{
"name": "spdlog",
"buildsystem": "cmake-ninja",
"config-opts": [
"-DCMAKE_BUILD_TYPE=Release",
"-DSPDLOG_BUILD_EXAMPLES=0",
"-DSPDLOG_BUILD_BENCH=0",
"-DSPDLOG_BUILD_TESTING=0"
],
"sources": [
{
"sha256": "3dbcbfd8c07e25f5e0d662b194d3a7772ef214358c49ada23c044c4747ce8b19",
"type": "archive",
"url": "https://github.com/gabime/spdlog/archive/v1.1.0.tar.gz"
}
]
},
{
"config-opts": [
"-DCMAKE_BUILD_TYPE=Release"
],
"buildsystem": "cmake-ninja",
"name": "olm",
"sources": [
{
"commit": "6753595300767dd70150831dbbe6f92d64e75038",
"disable-shallow-clone": true,
"tag": "3.1.4",
"type": "git",
"url": "https://gitlab.matrix.org/matrix-org/olm.git"
}
]
},
{
"config-opts":[
"-DJSON_BuildTests=OFF"
],
"buildsystem":"cmake",
"name": "nlohmann",
"sources":[
{
"sha256": "d51a3a8d3efbb1139d7608e28782ea9efea7e7933157e8ff8184901efd8ee760",
"type": "archive",
"url": "https://github.com/nlohmann/json/archive/v3.7.0.tar.gz"
}
]
},
{
"name": "sodium",
"sources": [
{
"sha256": "6f504490b342a4f8a4c4a02fc9b866cbef8622d5df4e5452b46be121e46636c1",
"type": "archive",
"url": "https://github.com/jedisct1/libsodium/releases/download/1.0.18-RELEASE/libsodium-1.0.18.tar.gz"
}
]
},
{
"build-commands": [
"./bootstrap.sh --with-libraries=thread,system,iostreams --prefix=/app",
"./b2 -d0 variant=release link=static threading=multi --layout=system",
"./b2 -d0 install"
],
"buildsystem": "simple",
"name": "boost",
"sources": [
{
"sha256": "59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722",
"type": "archive",
"url": "https://dl.bintray.com/boostorg/release/1.72.0/source/boost_1_72_0.tar.bz2"
}
]
},
{
"config-opts": [
"-DBUILD_LIB_TESTS=OFF",
"-DBUILD_LIB_EXAMPLES=OFF",
"-DCMAKE_BUILD_TYPE=Release",
"-DBUILD_SHARED_LIBS=OFF"
],
"buildsystem": "cmake-ninja",
"name": "mtxclient",
"sources": [
{
"sha256": "df3fe7e3d59b5fc52ee3ca9a132a55fc325aa799c676e9e420073c56daeb1848",
"type": "archive",
"url": "https://github.com/Nheko-Reborn/mtxclient/archive/5838f607d0e4c7595439249e8b9c213aec0667e9.tar.gz"
}
]
},
{
"config-opts": [
"-DCMAKE_BUILD_TYPE=Release",
"-DTWEENY_BUILD_DOCUMENTATION=OFF",
"-DTWEENY_BUILD_EXAMPLES=OFF"
],
"buildsystem": "cmake-ninja",
"name": "tweeny",
"sources": [
{
"sha256": "482857256a7235646004682912badb6521d361ed6987c8ebdae7986bf64ce694",
"type": "archive",
"url": "https://github.com/mobius3/tweeny/archive/43f4130f7e4a67c19d870b60864bc2862c19b81f.tar.gz"
}
]
},
{
"config-opts": [
"-DCMAKE_BUILD_TYPE=Release",
"-DLMDBXX_INCLUDE_DIR=.deps/lmdbxx"
],
"buildsystem": "cmake-ninja",
"name": "nheko",
"sources": [
{
"path": ".",
"type": "dir",
"skip": ["build-flatpak"]
},
{
"dest": ".deps/lmdbxx",
"sha256": "93721132bbf5045d38ad62de2997655e9984c48ea5c9886746d42128f4b26fbd",
"type": "archive",
"url": "https://github.com/bendiken/lmdbxx/archive/0b43ca87d8cfabba392dfe884eb1edb83874de02.tar.gz"
}
]
}
]
}

4446
resources/emoji-test.txt Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="at" class="svg-inline--fa fa-at fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M256 8C118.941 8 8 118.919 8 256c0 137.059 110.919 248 248 248 48.154 0 95.342-14.14 135.408-40.223 12.005-7.815 14.625-24.288 5.552-35.372l-10.177-12.433c-7.671-9.371-21.179-11.667-31.373-5.129C325.92 429.757 291.314 440 256 440c-101.458 0-184-82.542-184-184S154.542 72 256 72c100.139 0 184 57.619 184 160 0 38.786-21.093 79.742-58.17 83.693-17.349-.454-16.91-12.857-13.476-30.024l23.433-121.11C394.653 149.75 383.308 136 368.225 136h-44.981a13.518 13.518 0 0 0-13.432 11.993l-.01.092c-14.697-17.901-40.448-21.775-59.971-21.775-74.58 0-137.831 62.234-137.831 151.46 0 65.303 36.785 105.87 96 105.87 26.984 0 57.369-15.637 74.991-38.333 9.522 34.104 40.613 34.103 70.71 34.103C462.609 379.41 504 307.798 504 232 504 95.653 394.023 8 256 8zm-21.68 304.43c-22.249 0-36.07-15.623-36.07-40.771 0-44.993 30.779-72.729 58.63-72.729 22.292 0 35.601 15.241 35.601 40.77 0 45.061-33.875 72.73-58.161 72.73z"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1369
resources/langs/nheko_fi.ts Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1368
resources/langs/nheko_ja.ts Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 728 B

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

155
resources/nheko.svg Normal file
View file

@ -0,0 +1,155 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1024"
height="1024"
viewBox="0 0 270.93333 270.93333"
version="1.1"
id="svg8"
inkscape:version="0.92.4 5da689c313, 2019-01-14"
sodipodi:docname="nheko.svg"
inkscape:export-filename="/home/nicolas/Dokumente/devel/open-source/nheko/resources/nheko-rebuild-round-corners.svg.png"
inkscape:export-xdpi="130.048"
inkscape:export-ydpi="130.048">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.35355339"
inkscape:cx="852.07808"
inkscape:cy="-60.410565"
inkscape:document-units="mm"
inkscape:current-layer="layer2"
showgrid="true"
inkscape:window-width="1920"
inkscape:window-height="1019"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
showguides="true"
inkscape:snap-grids="true"
gridtolerance="10"
inkscape:snap-bbox="false"
inkscape:bbox-paths="true"
inkscape:snap-global="true"
inkscape:bbox-nodes="true"
inkscape:lockguides="false"
units="px">
<sodipodi:guide
position="0,0"
orientation="0,793.70079"
id="guide4797"
inkscape:locked="false" />
<sodipodi:guide
position="0,297"
orientation="1122.5197,0"
id="guide4803"
inkscape:locked="false" />
<inkscape:grid
type="axonomgrid"
id="grid4805"
units="px"
empspacing="2"
snapvisiblegridlinesonly="true"
spacingy="1.0583333" />
<sodipodi:guide
position="0,0"
orientation="0,755.90551"
id="guide4807"
inkscape:locked="false" />
<sodipodi:guide
position="200,0"
orientation="-755.90551,0"
id="guide4809"
inkscape:locked="false" />
<sodipodi:guide
position="200,200"
orientation="0,-755.90551"
id="guide4811"
inkscape:locked="false" />
<inkscape:grid
type="xygrid"
id="grid871"
empspacing="2"
color="#d43fff"
opacity="0.1254902"
empcolor="#cf3fff"
empopacity="0.25098039"
units="px"
spacingx="1.0583333"
spacingy="1.0583333"
enabled="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Logo"
style="display:inline"
transform="translate(0,-26.066668)">
<circle
id="path3792"
cx="135.46666"
cy="161.53333"
style="display:inline;fill:#333333;fill-opacity:1;stroke:none;stroke-width:0.3584221"
inkscape:transform-center-x="-57.929751"
inkscape:transform-center-y="532.03976"
inkscape:export-xdpi="96.000008"
inkscape:export-ydpi="96.000008"
r="135.46666" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.32663074px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 48.965212,110.73276 H 239.52342 c 4.88824,0 4.88824,0 0,8.46688 L 180.59519,221.2662 c -4.6188,8.00001 -4.6188,8.00001 -9.50702,8.00001 h -19.55294 c -4.88824,0 -4.88824,0 -0.26944,-8.00001 l 44.2635,-76.66608 h -29.41224 l -43.91123,76.19952 c -4.88823,8.46657 -4.88823,8.46657 -9.77646,8.46657 H 29.329398 l 49.299816,-84.66609 h -49.29982 z"
id="path4834"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccccccc"
inkscape:export-xdpi="96.000008"
inkscape:export-ydpi="96.000008" />
<path
style="fill:#c0def5;fill-opacity:1;stroke:none;stroke-width:0.3584221px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 97.764652,110.73276 H 127.09406 L 58.658797,229.26621 H 29.329398 Z"
id="path4836"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc"
inkscape:export-xdpi="96.000008"
inkscape:export-ydpi="96.000008" />
<path
style="fill:#87aade;fill-opacity:1;stroke:none;stroke-width:0.3584221px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 58.658797,229.26621 127.09406,110.73276 h 29.3294 L 87.988193,229.26621 Z"
id="path4838"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc"
inkscape:export-xdpi="96.000008"
inkscape:export-ydpi="96.000008" />
</g>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
style="display:inline"
transform="translate(0,-26.066668)" />
</svg>

After

Width:  |  Height:  |  Size: 5.2 KiB

53
resources/qml/Avatar.qml Normal file
View file

@ -0,0 +1,53 @@
import QtQuick 2.6
import QtGraphicalEffects 1.0
import Qt.labs.settings 1.0
Rectangle {
id: avatar
width: 48
height: 48
radius: settings.avatar_circles ? height/2 : 3
Settings {
id: settings
category: "user"
property bool avatar_circles: true
}
property alias url: img.source
property string displayName
Text {
anchors.fill: parent
text: chat.model.escapeEmoji(String.fromCodePoint(displayName.codePointAt(0)))
textFormat: Text.RichText
color: colors.text
font.pixelSize: avatar.height/2
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
visible: img.status != Image.Ready
}
Image {
id: img
anchors.fill: parent
asynchronous: true
fillMode: Image.PreserveAspectCrop
mipmap: true
smooth: false
sourceSize.width: avatar.width
sourceSize.height: avatar.height
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Rectangle {
anchors.fill: parent
width: avatar.width
height: avatar.height
radius: settings.avatar_circles ? height/2 : 3
}
}
}
color: colors.base
}

View file

@ -0,0 +1,26 @@
import QtQuick 2.5
import QtQuick.Controls 2.1
import im.nheko 1.0
Rectangle {
id: indicator
color: "transparent"
width: 16
height: 16
ToolTip.visible: ma.containsMouse && indicator.visible
ToolTip.text: qsTr("Encrypted")
MouseArea{
id: ma
anchors.fill: parent
hoverEnabled: true
}
Image {
id: stateImg
anchors.fill: parent
source: "image://colorimage/:/icons/icons/ui/lock.png?"+colors.buttonText
}
}

View file

@ -0,0 +1,29 @@
import QtQuick 2.3
import QtQuick.Controls 2.3
Button {
property string image: undefined
id: button
flat: true
// disable background, because we don't want a border on hover
background: Item {
}
Image {
id: buttonImg
// Workaround, can't get icon.source working for now...
anchors.fill: parent
source: "image://colorimage/" + image + "?" + (button.hovered ? colors.highlight : colors.buttonText)
}
MouseArea
{
id: mouseArea
anchors.fill: parent
onPressed: mouse.accepted = false
cursorShape: Qt.PointingHandCursor
}
}

View file

@ -0,0 +1,32 @@
import QtQuick 2.5
import QtQuick.Controls 2.3
TextEdit {
textFormat: TextEdit.RichText
readOnly: true
wrapMode: Text.Wrap
selectByMouse: true
color: colors.text
onLinkActivated: {
if (/^https:\/\/matrix.to\/#\/(@.*)$/.test(link)) chat.model.openUserProfile(/^https:\/\/matrix.to\/#\/(@.*)$/.exec(link)[1])
else if (/^https:\/\/matrix.to\/#\/(![^\/]*)$/.test(link)) timelineManager.setHistoryView(/^https:\/\/matrix.to\/#\/(!.*)$/.exec(link)[1])
else if (/^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.test(link)) {
var match = /^https:\/\/matrix.to\/#\/(![^\/]*)\/(\$.*)$/.exec(link)
timelineManager.setHistoryView(match[1])
chat.positionViewAtIndex(chat.model.idToIndex(match[2]), ListView.Contain)
}
else Qt.openUrlExternally(link)
}
MouseArea
{
id: ma
anchors.fill: parent
propagateComposedEvents: true
acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
ToolTip.visible: hoveredLink
ToolTip.text: hoveredLink
}

View file

@ -0,0 +1,39 @@
import QtQuick 2.5
import QtQuick.Controls 2.1
import im.nheko 1.0
Rectangle {
id: indicator
property int state: 0
color: "transparent"
width: 16
height: 16
ToolTip.visible: ma.containsMouse && state != MtxEvent.Empty
ToolTip.text: switch (state) {
case MtxEvent.Failed: return qsTr("Failed")
case MtxEvent.Sent: return qsTr("Sent")
case MtxEvent.Received: return qsTr("Received")
case MtxEvent.Read: return qsTr("Read")
default: return ""
}
MouseArea{
id: ma
anchors.fill: parent
hoverEnabled: true
}
Image {
id: stateImg
// Workaround, can't get icon.source working for now...
anchors.fill: parent
source: switch (indicator.state) {
case MtxEvent.Failed: return "image://colorimage/:/icons/icons/ui/remove-symbol.png?" + colors.buttonText
case MtxEvent.Sent: return "image://colorimage/:/icons/icons/ui/clock.png?" + colors.buttonText
case MtxEvent.Received: return "image://colorimage/:/icons/icons/ui/checkmark.png?" + colors.buttonText
case MtxEvent.Read: return "image://colorimage/:/icons/icons/ui/double-tick-indicator.png?" + colors.buttonText
default: return ""
}
}
}

View file

@ -0,0 +1,134 @@
import QtQuick 2.6
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import QtQuick.Window 2.2
import im.nheko 1.0
import "./delegates"
MouseArea {
id: rowArea
anchors.left: parent.left
anchors.right: parent.right
height: row.height
hoverEnabled: true
preventStealing: true
propagateComposedEvents: true
acceptedButtons: Qt.NoButton
property bool showButtons: false
Timer {
running: rowArea.containsMouse
interval: 150
onTriggered: rowArea.state = "showButtons"
}
states: [
State {
name: "hideButtons"
when: !rowArea.containsMouse
PropertyChanges { target: rowArea; showButtons: false; }
},
State {
name: "showButtons"
PropertyChanges { target: rowArea; showButtons: true; }
}
]
RowLayout {
id: row
anchors.leftMargin: avatarSize + 4
anchors.left: parent.left
anchors.right: parent.right
Column {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
spacing: 4
// fancy reply, if this is a reply
Reply {
visible: model.replyTo
modelData: chat.model.getDump(model.replyTo)
userColor: timelineManager.userColor(modelData.userId, colors.window)
}
// actual message content
MessageDelegate {
id: contentItem
width: parent.width
modelData: model
}
}
ImageButton {
visible: rowArea.showButtons
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
width: 16
id: replyButton
hoverEnabled: true
image: ":/icons/icons/ui/mail-reply.png"
ToolTip.visible: hovered
ToolTip.text: qsTr("Reply")
onClicked: chat.model.replyAction(model.id)
}
ImageButton {
visible: rowArea.showButtons
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
width: 16
id: optionsButton
hoverEnabled: true
image: ":/icons/icons/ui/vertical-ellipsis.png"
ToolTip.visible: hovered
ToolTip.text: qsTr("Options")
onClicked: messageContextMenu.show(model.id, model.type, optionsButton)
}
StatusIndicator {
state: model.state
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
width: 16
}
EncryptionIndicator {
visible: model.isEncrypted
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.preferredHeight: 16
width: 16
}
Text {
Layout.alignment: Qt.AlignRight | Qt.AlignTop
text: model.timestamp.toLocaleTimeString("HH:mm")
color: inactiveColors.text
MouseArea{
id: ma
anchors.fill: parent
hoverEnabled: true
}
ToolTip.visible: ma.containsMouse
ToolTip.text: Qt.formatDateTime(model.timestamp, Qt.DefaultLocaleLongDate)
}
}
}

View file

@ -0,0 +1,309 @@
import QtQuick 2.9
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import QtGraphicalEffects 1.0
import QtQuick.Window 2.2
import im.nheko 1.0
import "./delegates"
Item {
property var colors: currentActivePalette
property var systemInactive: SystemPalette { colorGroup: SystemPalette.Disabled }
property var inactiveColors: currentInactivePalette ? currentInactivePalette : systemInactive
property int avatarSize: 40
Menu {
id: messageContextMenu
palette: colors
modal: true
function show(eventId_, eventType_, showAt) {
eventId = eventId_
eventType = eventType_
popup(showAt)
}
property string eventId
property int eventType
MenuItem {
text: qsTr("Read receipts")
onTriggered: chat.model.readReceiptsAction(messageContextMenu.eventId)
}
MenuItem {
text: qsTr("Mark as read")
}
MenuItem {
text: qsTr("View raw message")
onTriggered: chat.model.viewRawMessage(messageContextMenu.eventId)
}
MenuItem {
text: qsTr("Redact message")
onTriggered: chat.model.redactEvent(messageContextMenu.eventId)
}
MenuItem {
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
text: qsTr("Save as")
onTriggered: timelineManager.timeline.saveMedia(messageContextMenu.eventId)
}
}
id: timelineRoot
Rectangle {
anchors.fill: parent
color: colors.window
Text {
visible: !timelineManager.timeline && !timelineManager.isInitialSync
anchors.centerIn: parent
text: qsTr("No room open")
font.pointSize: 24
color: colors.windowText
}
BusyIndicator {
anchors.centerIn: parent
running: timelineManager.isInitialSync
height: 200
width: 200
z: 3
}
ListView {
id: chat
visible: timelineManager.timeline != null
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: chatFooter.top
anchors.leftMargin: 4
anchors.rightMargin: scrollbar.width
model: timelineManager.timeline
boundsBehavior: Flickable.StopAtBounds
pixelAligned: true
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
propagateComposedEvents: true
z: -1
onWheel: {
if (wheel.angleDelta != 0) {
chat.contentY = chat.contentY - wheel.angleDelta.y
wheel.accepted = true
chat.returnToBounds()
}
}
}
Shortcut {
sequence: StandardKey.MoveToPreviousPage
onActivated: { chat.contentY = chat.contentY - chat.height / 2; chat.returnToBounds(); }
}
Shortcut {
sequence: StandardKey.MoveToNextPage
onActivated: { chat.contentY = chat.contentY + chat.height / 2; chat.returnToBounds(); }
}
ScrollBar.vertical: ScrollBar {
id: scrollbar
parent: chat.parent
anchors.top: chat.top
anchors.left: chat.right
anchors.bottom: chat.bottom
}
spacing: 4
verticalLayoutDirection: ListView.BottomToTop
onCountChanged: if (atYEnd) model.currentIndex = 0 // Mark last event as read, since we are at the bottom
delegate: Rectangle {
// This would normally be previousSection, but our model's order is inverted.
property bool sectionBoundary: (ListView.nextSection != "" && ListView.nextSection !== ListView.section) || model.index === chat.count - 1
id: wrapper
property Item section
width: chat.width
height: section ? section.height + timelinerow.height : timelinerow.height
color: "transparent"
TimelineRow {
id: timelinerow
y: section ? section.y + section.height : 0
}
onSectionBoundaryChanged: {
if (sectionBoundary) {
var properties = {
'modelData': model.dump,
'section': ListView.section,
'nextSection': ListView.nextSection
}
section = sectionHeader.createObject(wrapper, properties)
} else {
section.destroy()
section = null
}
}
Binding {
target: chat.model
property: "currentIndex"
when: y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height
value: index
delayed: true
}
}
section {
property: "section"
}
Component {
id: sectionHeader
Column {
property var modelData
property string section
property string nextSection
topPadding: 4
bottomPadding: 4
spacing: 8
visible: !!modelData
width: parent.width
height: (section.includes(" ") ? dateBubble.height + 8 + userName.height : userName.height) + 8
Label {
id: dateBubble
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
visible: section.includes(" ")
text: chat.model.formatDateSeparator(modelData.timestamp)
color: colors.windowText
height: contentHeight * 1.2
width: contentWidth * 1.2
horizontalAlignment: Text.AlignHCenter
background: Rectangle {
radius: parent.height / 2
color: colors.base
}
}
Row {
height: userName.height
spacing: 4
Avatar {
width: avatarSize
height: avatarSize
url: chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/")
displayName: modelData.userName
MouseArea {
anchors.fill: parent
onClicked: chat.model.openUserProfile(modelData.userId)
cursorShape: Qt.PointingHandCursor
}
}
Text {
id: userName
text: chat.model.escapeEmoji(modelData.userName)
color: timelineManager.userColor(modelData.userId, colors.window)
textFormat: Text.RichText
MouseArea {
anchors.fill: parent
onClicked: chat.model.openUserProfile(section.split(" ")[0])
cursorShape: Qt.PointingHandCursor
}
}
}
}
}
}
Rectangle {
id: chatFooter
height: Math.max(16, footerContent.height)
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
z: 3
color: "transparent"
Column {
id: footerContent
anchors.left: parent.left
anchors.right: parent.right
Text {
id: typingDisplay
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 10
anchors.rightMargin: 10
text: chat.model ? chat.model.formatTypingUsers(chat.model.typingUsers, colors.window) : ""
textFormat: Text.RichText
color: colors.windowText
}
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
id: replyPopup
visible: timelineManager.replyingEvent && chat.model
// Height of child, plus margins, plus border
height: replyPreview.height + 10
color: colors.base
Reply {
id: replyPreview
anchors.left: parent.left
anchors.leftMargin: 10
anchors.right: closeReplyButton.left
anchors.rightMargin: 20
anchors.bottom: parent.bottom
modelData: chat.model ? chat.model.getDump(timelineManager.replyingEvent) : {}
userColor: timelineManager.userColor(modelData.userId, colors.window)
}
ImageButton {
id: closeReplyButton
anchors.right: parent.right
anchors.rightMargin: 15
anchors.top: replyPreview.top
hoverEnabled: true
width: 16
height: 16
image: ":/icons/icons/ui/remove-symbol.png"
ToolTip.visible: closeReplyButton.hovered
ToolTip.text: qsTr("Close")
onClicked: timelineManager.closeReply()
}
}
}
}
}
}

View file

@ -0,0 +1,57 @@
import QtQuick 2.6
import QtQuick.Layouts 1.2
Rectangle {
radius: 10
color: colors.base
height: row.height + 24
width: parent ? parent.width : undefined
RowLayout {
id: row
anchors.centerIn: parent
width: parent.width - 24
spacing: 15
Rectangle {
id: button
color: colors.light
radius: 22
height: 44
width: 44
Image {
id: img
anchors.centerIn: parent
source: "qrc:/icons/icons/ui/arrow-pointing-down.png"
fillMode: Image.Pad
}
MouseArea {
anchors.fill: parent
onClicked: timelineManager.timeline.saveMedia(model.data.id)
cursorShape: Qt.PointingHandCursor
}
}
ColumnLayout {
id: col
Text {
Layout.fillWidth: true
text: model.data.body
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text
}
Text {
Layout.fillWidth: true
text: model.data.filesize
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text
}
}
}
}

View file

@ -0,0 +1,28 @@
import QtQuick 2.6
import im.nheko 1.0
Item {
property double tempWidth: Math.min(parent ? parent.width : undefined, model.data.width)
property double tempHeight: tempWidth * model.data.proportionalHeight
property bool tooHigh: tempHeight > timelineRoot.height / 2
height: tooHigh ? timelineRoot.height / 2 : tempHeight
width: tooHigh ? (timelineRoot.height / 2) / model.data.proportionalHeight : tempWidth
Image {
id: img
anchors.fill: parent
source: model.data.url.replace("mxc://", "image://MxcImage/")
asynchronous: true
fillMode: Image.PreserveAspectFit
MouseArea {
enabled: model.data.type == MtxEvent.ImageMessage
anchors.fill: parent
onClicked: timelineManager.openImageOverlay(model.data.url, model.data.id)
}
}
}

View file

@ -0,0 +1,94 @@
import QtQuick 2.6
import im.nheko 1.0
Item {
// Workaround to have an assignable global property
Item {
id: model
property var data;
}
property alias modelData: model.data
height: chooser.childrenRect.height
DelegateChooser {
id: chooser
//role: "type" //< not supported in our custom implementation, have to use roleValue
roleValue: model.data.type
anchors.fill: parent
DelegateChoice {
roleValue: MtxEvent.UnknownMessage
Placeholder { text: "Unretrieved event" }
}
DelegateChoice {
roleValue: MtxEvent.TextMessage
TextMessage {}
}
DelegateChoice {
roleValue: MtxEvent.NoticeMessage
NoticeMessage {}
}
DelegateChoice {
roleValue: MtxEvent.EmoteMessage
NoticeMessage {
formatted: chat.model.escapeEmoji(modelData.userName) + " " + model.data.formattedBody
color: timelineManager.userColor(modelData.userId, colors.window)
}
}
DelegateChoice {
roleValue: MtxEvent.ImageMessage
ImageMessage {}
}
DelegateChoice {
roleValue: MtxEvent.Sticker
ImageMessage {}
}
DelegateChoice {
roleValue: MtxEvent.FileMessage
FileMessage {}
}
DelegateChoice {
roleValue: MtxEvent.VideoMessage
PlayableMediaMessage {}
}
DelegateChoice {
roleValue: MtxEvent.AudioMessage
PlayableMediaMessage {}
}
DelegateChoice {
roleValue: MtxEvent.Redacted
Pill {
text: qsTr("redacted")
}
}
DelegateChoice {
roleValue: MtxEvent.Encryption
Pill {
text: qsTr("Encryption enabled")
}
}
DelegateChoice {
roleValue: MtxEvent.Name
NoticeMessage {
text: model.data.roomName ? qsTr("room name changed to: %1").arg(model.data.roomName) : qsTr("removed room name")
}
}
DelegateChoice {
roleValue: MtxEvent.Topic
NoticeMessage {
text: model.data.roomTopic ? qsTr("topic changed to: %1").arg(model.data.roomTopic) : qsTr("removed topic")
}
}
DelegateChoice {
roleValue: MtxEvent.Member
NoticeMessage {
text: timelineManager.timeline.formatMemberEvent(model.data.id);
}
}
DelegateChoice {
Placeholder {}
}
}
}

View file

@ -0,0 +1,4 @@
TextMessage {
font.italic: true
color: inactiveColors.text
}

View file

@ -0,0 +1,14 @@
import QtQuick 2.5
import QtQuick.Controls 2.1
Label {
color: inactiveColors.text
horizontalAlignment: Text.AlignHCenter
height: contentHeight * 1.2
width: contentWidth * 1.2
background: Rectangle {
radius: parent.height / 2
color: colors.base
}
}

View file

@ -0,0 +1,7 @@
import ".."
MatrixText {
text: qsTr("unimplemented event: ") + model.data.typeString
width: parent ? parent.width : undefined
color: inactiveColors.text
}

View file

@ -0,0 +1,167 @@
import QtQuick 2.6
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.1
import QtMultimedia 5.6
import im.nheko 1.0
Rectangle {
id: bg
radius: 10
color: colors.base
height: content.height + 24
width: parent ? parent.width : undefined
Column {
id: content
width: parent.width - 24
anchors.centerIn: parent
Rectangle {
id: videoContainer
visible: model.data.type == MtxEvent.VideoMessage
width: Math.min(parent.width, model.data.width ? model.data.width : 400) // some media has 0 as size...
height: width*model.data.proportionalHeight
Image {
anchors.fill: parent
source: model.data.thumbnailUrl.replace("mxc://", "image://MxcImage/")
asynchronous: true
fillMode: Image.PreserveAspectFit
VideoOutput {
anchors.fill: parent
fillMode: VideoOutput.PreserveAspectFit
source: media
}
}
}
RowLayout {
width: parent.width
Text {
id: positionText
text: "--:--:--"
color: colors.text
}
Slider {
Layout.fillWidth: true
id: progress
value: media.position
from: 0
to: media.duration
onMoved: media.seek(value)
//indeterminate: true
function updatePositionTexts() {
function formatTime(date) {
var hh = date.getUTCHours();
var mm = date.getUTCMinutes();
var ss = date.getSeconds();
if (hh < 10) {hh = "0"+hh;}
if (mm < 10) {mm = "0"+mm;}
if (ss < 10) {ss = "0"+ss;}
return hh+":"+mm+":"+ss;
}
positionText.text = formatTime(new Date(media.position))
durationText.text = formatTime(new Date(media.duration))
}
onValueChanged: updatePositionTexts()
palette: colors
}
Text {
id: durationText
text: "--:--:--"
color: colors.text
}
}
RowLayout {
width: parent.width
spacing: 15
Rectangle {
id: button
color: colors.window
radius: 22
height: 44
width: 44
Image {
id: img
anchors.centerIn: parent
z: 3
source: "image://colorimage/:/icons/icons/ui/arrow-pointing-down.png?"+colors.text
fillMode: Image.Pad
}
MouseArea {
anchors.fill: parent
onClicked: {
switch (button.state) {
case "": timelineManager.timeline.cacheMedia(model.data.id); break;
case "stopped":
media.play(); console.log("play");
button.state = "playing"
break
case "playing":
media.pause(); console.log("pause");
button.state = "stopped"
break
}
}
cursorShape: Qt.PointingHandCursor
}
MediaPlayer {
id: media
onError: console.log(errorString)
onStatusChanged: if(status == MediaPlayer.Loaded) progress.updatePositionTexts()
onStopped: button.state = "stopped"
}
Connections {
target: timelineManager.timeline
onMediaCached: {
if (mxcUrl == model.data.url) {
media.source = "file://" + cacheUrl
button.state = "stopped"
console.log("media loaded: " + mxcUrl + " at " + cacheUrl)
}
console.log("media cached: " + mxcUrl + " at " + cacheUrl)
}
}
states: [
State {
name: "stopped"
PropertyChanges { target: img; source: "image://colorimage/:/icons/icons/ui/play-sign.png?"+colors.text }
},
State {
name: "playing"
PropertyChanges { target: img; source: "image://colorimage/:/icons/icons/ui/pause-symbol.png?"+colors.text }
}
]
}
ColumnLayout {
id: col
Text {
Layout.fillWidth: true
text: model.data.body
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text
}
Text {
Layout.fillWidth: true
text: model.data.filesize
textFormat: Text.PlainText
elide: Text.ElideRight
color: colors.text
}
}
}
}
}

View file

@ -0,0 +1,58 @@
import QtQuick 2.6
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import QtQuick.Window 2.2
Rectangle {
id: replyComponent
property alias modelData: reply.modelData
property color userColor: "red"
width: parent.width
height: replyContainer.height
MouseArea {
anchors.fill: parent
preventStealing: true
onClicked: chat.positionViewAtIndex(chat.model.idToIndex(timelineManager.replyingEvent), ListView.Contain)
cursorShape: Qt.PointingHandCursor
}
Rectangle {
id: colorLine
anchors.top: replyContainer.top
anchors.bottom: replyContainer.bottom
width: 4
color: timelineManager.userColor(reply.modelData.userId, colors.window)
}
Column {
id: replyContainer
anchors.left: colorLine.right
anchors.leftMargin: 4
width: parent.width - 8
Text {
id: userName
text: chat.model ? chat.model.escapeEmoji(reply.modelData.userName) : ""
color: replyComponent.userColor
textFormat: Text.RichText
MouseArea {
anchors.fill: parent
onClicked: chat.model.openUserProfile(reply.modelData.userId)
cursorShape: Qt.PointingHandCursor
}
}
MessageDelegate {
id: reply
width: parent.width
}
}
color: Qt.rgba(userColor.r, userColor.g, userColor.b, 0.2)
}

View file

@ -0,0 +1,7 @@
import ".."
MatrixText {
property string formatted: model.data.formattedBody
text: "<style type=\"text/css\">a { color:"+colors.link+";}</style>" + formatted.replace("<pre>", "<pre style='white-space: pre-wrap'>")
width: parent ? parent.width : undefined
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,5 +1,6 @@
<RCC>
<qresource prefix="/icons">
<file>icons/ui/at-solid.svg</file>
<file>icons/ui/volume-off-indicator.png</file>
<file>icons/ui/volume-off-indicator@2x.png</file>
<file>icons/ui/black-bubble-speech.png</file>
@ -63,6 +64,8 @@
<file>icons/ui/edit.png</file>
<file>icons/ui/edit@2x.png</file>
<file>icons/ui/mail-reply.png</file>
<file>icons/emoji-categories/people.png</file>
<file>icons/emoji-categories/people@2x.png</file>
<file>icons/emoji-categories/nature.png</file>
@ -82,6 +85,7 @@
</qresource>
<qresource prefix="/logos">
<file>nheko.png</file>
<file>nheko.svg</file>
<file>splash.png</file>
<file>splash@2x.png</file>
@ -99,16 +103,27 @@
<file>nheko-32.png</file>
<file>nheko-16.png</file>
</qresource>
<qresource prefix="/fonts">
<file>fonts/OpenSans/OpenSans-Regular.ttf</file>
<file>fonts/OpenSans/OpenSans-Italic.ttf</file>
<file>fonts/OpenSans/OpenSans-Bold.ttf</file>
<file>fonts/OpenSans/OpenSans-Semibold.ttf</file>
<file>fonts/EmojiOne/emojione-android.ttf</file>
</qresource>
<qresource prefix="/styles">
<file>styles/system.qss</file>
<file>styles/nheko.qss</file>
<file>styles/nheko-dark.qss</file>
</qresource>
<qresource prefix="/">
<file>qml/TimelineView.qml</file>
<file>qml/Avatar.qml</file>
<file>qml/ImageButton.qml</file>
<file>qml/MatrixText.qml</file>
<file>qml/StatusIndicator.qml</file>
<file>qml/EncryptionIndicator.qml</file>
<file>qml/TimelineRow.qml</file>
<file>qml/delegates/MessageDelegate.qml</file>
<file>qml/delegates/TextMessage.qml</file>
<file>qml/delegates/NoticeMessage.qml</file>
<file>qml/delegates/ImageMessage.qml</file>
<file>qml/delegates/PlayableMediaMessage.qml</file>
<file>qml/delegates/FileMessage.qml</file>
<file>qml/delegates/Pill.qml</file>
<file>qml/delegates/Placeholder.qml</file>
<file>qml/delegates/Reply.qml</file>
</qresource>
</RCC>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -3,13 +3,75 @@ QLabel {
color: #caccd1;
}
TimelineItem {
qproperty-backgroundColor: #202228;
TextLabel::a {
color: #38a3d8;
}
QuickSwitcher,
ReplyPopup,
SuggestionsPopup,
UserSettingsPage,
#scroll_widget,
#UserSettingScrollWidget {
background-color: #202228;
}
#chatPage,
#chatPage > * {
#chatPage > *,
CommunitiesList,
CommunitiesList > *,
RoomList,
RoomList > *,
TimelineView,
TimelineView > *,
UserMentionsWidget,
UserMentionsWidget > * {
background-color: #2d3139;
border: none;
}
QLineEdit,
QListWidget,
WelcomePage,
LoginPage,
RegisterPage,
EditModal,
emoji--Panel,
emoji--Panel > *,
dialogs--Logout,
dialogs--ReCaptcha,
dialogs--LeaveRoom,
dialogs--CreateRoom,
dialogs--RoomSettings,
dialogs--InviteUsers,
dialogs--ReadReceipts,
dialogs--JoinRoom,
dialogs--MemberList,
dialogs--PreviewUploadOverlay,
dialogs--UserProfile,
dialogs--CreateRoom > QLineEdit,
dialogs--InviteUsers > QLineEdit,
dialogs--JoinRoom > QLineEdit {
background-color: #202228;
color: #caccd1;
}
emoji--Panel QWidget { border: none; }
emoji--Panel QScrollBar:vertical { width: 0px; margin: 0px; }
emoji--Panel QScrollBar::handle:vertical { min-height: 30px; }
emoji--Category,
emoji--Category > * {
background-color: #2d3139;
color: #727274;
}
emoji--Category QLabel {
margin: 20px 0 20px 8px;
}
TimelineItem {
qproperty-backgroundColor: #202228;
}
#sideBar {
@ -18,18 +80,9 @@ TimelineItem {
border-left: 1px solid #202228;
}
TimelineView,
TimelineView > * {
background-color: #202228;
border: none;
}
#scroll_widget {
background-color: #202228;
}
QuickSwitcher {
background-color: #202228;
UserMentionsWidget > TimelineItem {
qproperty-backgroundColor: #202228;
qproperty-hoverColor: rgba(45, 49, 57, 120);
}
InfoMessage {
@ -37,21 +90,11 @@ InfoMessage {
qproperty-boxColor: rgba(45, 49, 57, 120);
}
SuggestionsPopup {
background-color: #202228;
}
PopupItem {
background-color: #202228;
qproperty-hoverColor: rgba(45, 49, 57, 120);
}
RoomList,
RoomList > * {
background-color: #2d3139;
border: none;
}
TypingDisplay {
qproperty-textColor: #caccd1;
qproperty-backgroundColor: #202228;
@ -61,35 +104,26 @@ TypingDisplay {
background-color: #2d3139;
}
CommunitiesList,
CommunitiesList > * {
background-color: #2d3139;
}
FlatButton {
qproperty-foregroundColor: #727274;
qproperty-backgroundColor: #333;
qproperty-disabledForegroundColor: #222;
}
AudioItem,
FileItem {
qproperty-textColor: #caccd1;
qproperty-backgroundColor: #2d3139;
qproperty-iconColor: #caccd1;
}
AudioItem {
qproperty-textColor: #caccd1;
qproperty-backgroundColor: #2d3139;
qproperty-iconColor: #caccd1;
}
RaisedButton {
qproperty-foregroundColor: #caccd1;
qproperty-backgroundColor: #333;
}
RoomInfoListItem {
RoomInfoListItem,
UserMentionsWidget {
qproperty-mentionedColor: #a82353;
qproperty-highlightedBackgroundColor: #4d84c7;
qproperty-hoverBackgroundColor: rgba(230, 230, 230, 30);
@ -111,13 +145,15 @@ RoomInfoListItem {
qproperty-highlightedTimestampColor: #e7e7e9;
qproperty-hoverTimestampColor: #f4f5f8;
qproperty-avatarBgColor: #202228;
qproperty-avatarFgColor: white;
qproperty-bubbleFgColor: white;
qproperty-bubbleBgColor: #4d84c7;
}
RoomInfoListItem > Avatar {
qproperty-backgroundColor: #202228;
qproperty-textColor: white;
}
CommunitiesListItem {
qproperty-highlightedBackgroundColor: #4d84c7;
qproperty-hoverBackgroundColor: rgba(230, 230, 230, 30);
@ -141,12 +177,12 @@ UserInfoWidget {
border-bottom: 1px solid #202228;
}
UserSettingsPage {
background-color: #202228;
#UserSettingScrollWidget > QComboBox {
color: #202228;
}
#UserSettingScrollWidget {
background-color: #202228;
#UserSettingScrollWidget > QComboBox {
color: #202228;
}
Avatar {
@ -154,55 +190,17 @@ Avatar {
qproperty-backgroundColor: #2d3139;
}
#displayNameLabel {
color: #f2f2f2;
}
#displayNameLabel,
#userIdLabel {
color: #f2f2f2;
}
dialogs--Logout,
dialogs--ReCaptcha,
dialogs--LeaveRoom,
dialogs--CreateRoom,
dialogs--RoomSettings,
dialogs--InviteUsers,
dialogs--ReadReceipts,
dialogs--JoinRoom,
dialogs--MemberList,
dialogs--PreviewUploadOverlay,
dialogs--UserProfile,
dialogs--CreateRoom > QLineEdit,
dialogs--InviteUsers > QLineEdit,
EditModal,
dialogs--JoinRoom > QLineEdit {
background-color: #202228;
color: #caccd1;
}
TopSection {
qproperty-textColor: #caccd1;
}
QListWidget,
WelcomePage,
LoginPage,
RegisterPage {
background-color: #202228;
color: #caccd1;
}
emoji--Panel,
emoji--Panel > * {
background-color: #202228;
color: #caccd1;
}
emoji--Category,
emoji--Category > * {
background-color: #2d3139;
color: #caccd1;
emoji--Category {
qproperty-hoverBackgroundColor: rgba(230, 230, 230, 30);
}
FloatingButton {
@ -221,23 +219,14 @@ ScrollBar {
qproperty-backgroundColor: #202228;
}
SideBarActions {
SideBarActions,
TopRoomBar
{
border: none;
border-top: 1px solid #202228;
background-color: #2d3139;
}
TopRoomBar {
border: none;
border-bottom: 1px solid #202228;
background-color: #2d3139;
}
QLineEdit {
background-color: #202228;
color: #caccd1;
}
TextInputWidget {
border: none;
border-top: 1px solid #2d3139;
@ -261,3 +250,5 @@ SnackBar {
qproperty-textColor: #caccd1;
qproperty-bgColor: #202228;
}
QSplitter::handle { image: none; }

View file

@ -3,13 +3,38 @@ QLabel {
color: #333;
}
TimelineItem {
qproperty-backgroundColor: white;
TextLabel::a {
color: #0077b5;
}
QuickSwitcher,
ReplyPopup,
SuggestionsPopup,
UserSettingsPage,
#scroll_widget,
#UserSettingScrollWidget {
background-color: white;
}
#chatPage,
#chatPage > * {
#chatPage > *,
CommunitiesList,
CommunitiesList > *,
RoomList,
RoomList > *,
TimelineView,
TimelineView > *,
UserMentionsWidget,
UserMentionsWidget > *,
TimelineView,
TimelineView > * {
background-color: white;
border: none;
}
TimelineItem {
qproperty-backgroundColor: white;
}
#sideBar {
@ -18,18 +43,9 @@ TimelineItem {
border-left: 1px solid #dee1f3;
}
TimelineView,
TimelineView > * {
background-color: white;
border: none;
}
#scroll_widget {
background-color: white;
}
QuickSwitcher {
background-color: white;
UserMentionsWidget > TimelineItem {
qproperty-backgroundColor: white;
qproperty-hoverColor: rgba(192, 193, 195, 120);
}
InfoMessage {
@ -42,17 +58,15 @@ TypingDisplay {
qproperty-backgroundColor: white;
}
SuggestionsPopup {
background-color: white;
}
PopupItem {
background-color: white;
qproperty-hoverColor: rgba(192, 193, 195, 120);
}
RoomList,
RoomList > * {
RoomList > *,
CommunitiesList,
CommunitiesList > * {
background-color: #2e3649;
border: none;
}
@ -61,27 +75,17 @@ RoomList > * {
background-color: #2e3649;
}
CommunitiesList,
CommunitiesList > * {
background-color: #2e3649;
}
FlatButton {
qproperty-foregroundColor: #495057;
}
AudioItem,
FileItem {
qproperty-textColor: #333;
qproperty-backgroundColor: #f2f2f2;
qproperty-iconColor: white;
}
AudioItem {
qproperty-textColor: #333;
qproperty-backgroundColor: #f2f2f2;
qproperty-iconColor: white;
}
RaisedButton {
qproperty-foregroundColor: white;
}
@ -89,7 +93,7 @@ RaisedButton {
RoomInfoListItem {
qproperty-mentionedColor: #a82353;
qproperty-highlightedBackgroundColor: #38A3D8;
qproperty-hoverBackgroundColor: rgba(200, 200, 200, 70);
qproperty-hoverBackgroundColor: rgba(200, 200, 200, 40);
qproperty-hoverTitleColor: #f2f5f8;
qproperty-hoverSubtitleColor: white;
qproperty-backgroundColor: #f2f5f8;
@ -107,16 +111,18 @@ RoomInfoListItem {
qproperty-highlightedTimestampColor: #f4f4f5;
qproperty-hoverTimestampColor: white;
qproperty-avatarBgColor: #eee;
qproperty-avatarFgColor: black;
qproperty-bubbleFgColor: white;
qproperty-bubbleBgColor: #38A3D8;
}
RoomInfoListItem > Avatar {
qproperty-backgroundColor: #eee;
qproperty-textColor: black;
}
CommunitiesListItem {
qproperty-highlightedBackgroundColor: #38A3D8;
qproperty-hoverBackgroundColor: rgba(200, 200, 200, 70);
qproperty-hoverBackgroundColor: rgba(200, 200, 200, 40);
qproperty-backgroundColor: #f2f5f8;
qproperty-avatarBgColor: #eee;
@ -141,14 +147,6 @@ UserInfoWidget {
border-bottom: 2px solid #ccc;
}
UserSettingsPage {
background-color: white;
}
#UserSettingScrollWidget {
background-color: white;
}
Avatar {
qproperty-textColor: black;
qproperty-backgroundColor: #eee;
@ -196,12 +194,22 @@ emoji--Panel > * {
color: #333;
}
emoji--Panel QWidget { border: none; }
emoji--Panel QScrollBar:vertical { width: 0px; margin: 0px; }
emoji--Panel QScrollBar::handle:vertical { min-height: 30px; }
emoji--Category {
qproperty-hoverBackgroundColor: rgba(200, 200, 200, 70);
}
emoji--Category,
emoji--Category > * {
background-color: white;
color: #ccc;
}
emoji--Category QLabel { margin: 20px 0 20px 8px; }
FloatingButton {
qproperty-backgroundColor: #efefef;
qproperty-foregroundColor: black;
@ -244,3 +252,5 @@ SnackBar {
qproperty-textColor: white;
qproperty-bgColor: #495057;
}
QSplitter::handle { image: none; }

View file

@ -1,3 +1,16 @@
#chatPage,
#chatPage > *,
CommunitiesList,
CommunitiesList > *,
RoomList,
RoomList > *,
TimelineView,
TimelineView > *,
UserMentionsWidget,
UserMentionsWidget > * {
border: none;
}
TypingDisplay {
qproperty-textColor: palette(text);
qproperty-backgroundColor: palette(window);
@ -7,21 +20,18 @@ TimelineItem {
qproperty-backgroundColor: palette(window);
}
TimelineView,
TimelineView > * {
border: none;
UserMentionsWidget > TimelineItem {
qproperty-backgroundColor: palette(window);
qproperty-hoverColor: palette(base);
}
SideBarActions,
TextInputWidget {
border: none;
border-top: 1px solid palette(mid);
}
SideBarActions {
border: none;
border-top: 1px solid palette(mid);
}
UserInfoWidget,
TopRoomBar {
border: none;
border-bottom: 1px solid palette(mid);
@ -33,11 +43,6 @@ RoomList > * {
border: none;
}
UserInfoWidget {
border: none;
border-bottom: 1px solid palette(mid);
}
#sideBar {
border: none;
border-right: 1px solid palette(mid);
@ -57,18 +62,13 @@ FlatButton {
qproperty-foregroundColor: palette(text);
}
AudioItem,
FileItem {
qproperty-textColor: palette(text);
qproperty-backgroundColor: palette(base);
qproperty-iconColor: palette(window);
}
AudioItem {
qproperty-textColor: palette(text);
qproperty-backgroundColor: palette(base);
qproperty-iconColor: palette(window);
}
RaisedButton {
qproperty-foregroundColor: palette(buttonText);
}
@ -85,10 +85,11 @@ QListWidget {
background-color: palette(window);
}
RoomInfoListItem {
RoomInfoListItem,
UserMentionsWidget {
qproperty-mentionedColor: palette(alternate-base);
qproperty-highlightedBackgroundColor: palette(highlight);
qproperty-hoverBackgroundColor: palette(base);
qproperty-hoverBackgroundColor: palette(light);
qproperty-backgroundColor: palette(window);
qproperty-titleColor: palette(text);
@ -107,16 +108,19 @@ RoomInfoListItem {
qproperty-highlightedTimestampColor: palette(highlightedtext);
qproperty-hoverTimestampColor: palette(highlightedtext);
qproperty-avatarBgColor: palette(base);
qproperty-avatarFgColor: palette(text);
qproperty-bubbleBgColor: palette(base);
qproperty-bubbleFgColor: palette(text);
}
RoomInfoListItem > Avatar {
qproperty-backgroundColor: palette(base);
qproperty-textColor: palette(text);
}
CommunitiesListItem {
qproperty-highlightedBackgroundColor: palette(highlight);
qproperty-hoverBackgroundColor: palette(base);
qproperty-hoverBackgroundColor: palette(light);
qproperty-backgroundColor: palette(window);
qproperty-avatarBgColor: palette(base);
@ -131,6 +135,30 @@ LoadingIndicator {
qproperty-color: palette(light);
}
emoji--Panel,
emoji--Panel > * {
background-color: palette(base);
color: palette(text);
}
emoji--Panel QWidget { border: none; }
emoji--Panel QScrollBar:vertical { width: 0px; margin: 0px; }
emoji--Panel QScrollBar::handle:vertical { min-height: 30px; }
emoji--Category {
qproperty-hoverBackgroundColor: palette(highlight);
}
emoji--Category,
emoji--Category > * {
background-color: palette(window);
color: palette(text);
}
emoji--Category QLabel {
margin: 20px 0 20px 8px;
}
FloatingButton {
qproperty-backgroundColor: palette(base);
qproperty-foregroundColor: palette(text);
@ -141,13 +169,11 @@ SnackBar {
qproperty-bgColor: palette(base);
}
MemberItem {
background-color: palette(window);
}
Toggle {
qproperty-activeColor: palette(highlight);
qproperty-disabledColor: palette(dark);
qproperty-inactiveColor: palette(mid);
qproperty-trackColor: palette(base);
}
QSplitter::handle { image: none; }

View file

@ -1,31 +1,19 @@
#!/usr/bin/env python3
import sys
import json
import re
from jinja2 import Template
class Emoji(object):
def __init__(self, code, shortname, category, order):
self.code = ''.join(list(map(code_to_bytes, code.split('-'))))
def __init__(self, code, shortname):
self.code = repr(code.encode('utf-8'))[1:].strip("'")
self.shortname = shortname
self.category = category
self.order = int(order)
def code_to_bytes(codepoint):
'''
Convert hex unicode codepoint to hex byte array.
'''
bytes = chr(int(codepoint, 16)).encode('utf-8')
return str(bytes)[1:].strip("'")
def generate_code(emojis, category):
tmpl = Template('''
const QList<Emoji> EmojiProvider::{{ category }} = {
const std::vector<Emoji> emoji::Provider::{{ category }} = {
{%- for e in emoji %}
Emoji{QString::fromUtf8("{{ e.code }}"), "{{ e.shortname }}"},
{%- endfor %}
@ -38,44 +26,56 @@ const QList<Emoji> EmojiProvider::{{ category }} = {
if __name__ == '__main__':
if len(sys.argv) < 2:
print('usage: emoji_codegen.py /path/to/emoji.json')
print('usage: emoji_codegen.py /path/to/emoji-test.txt')
sys.exit(1)
filename = sys.argv[1]
data = {}
with open(filename, 'r') as filename:
data = json.loads(filename.read())
people = []
nature = []
food = []
activity = []
travel = []
objects = []
symbols = []
flags = []
emojis = []
categories = {
'Smileys & Emotion': people,
'People & Body': people,
'Animals & Nature': nature,
'Food & Drink': food,
'Travel & Places': travel,
'Activities': activity,
'Objects': objects,
'Symbols': symbols,
'Flags': flags
}
for emoji_name in data:
tmp = data[emoji_name]
current_category = ''
for line in open(filename, 'r'):
if line.startswith('# group:'):
current_category = line.split(':', 1)[1].strip()
l = len(tmp['unicode'].split('-'))
if l > 1 and tmp['category'] == 'people':
if not line or line.startswith('#'):
continue
emojis.append(
Emoji(
tmp['unicode'],
tmp['shortname'],
tmp['category'],
tmp['emoji_order']
)
)
segments = re.split(r'\s+[#;] ', line.strip())
if len(segments) != 3:
continue
emojis.sort(key=lambda x: x.order)
code, qualification, charAndName = segments
people = list(filter(lambda x: x.category == "people", emojis))
nature = list(filter(lambda x: x.category == "nature", emojis))
food = list(filter(lambda x: x.category == "food", emojis))
activity = list(filter(lambda x: x.category == "activity", emojis))
travel = list(filter(lambda x: x.category == "travel", emojis))
objects = list(filter(lambda x: x.category == "objects", emojis))
symbols = list(filter(lambda x: x.category == "symbols", emojis))
flags = list(filter(lambda x: x.category == "flags", emojis))
# skip fully qualified versions of same unicode
if code.endswith('FE0F'):
continue
if qualification == 'component':
continue
char, name = re.match(r'^(\S+) E\d+\.\d+ (.*)$', charAndName).groups()
categories[current_category].append(Emoji(char, name))
# Use xclip to pipe the output to clipboard.
# e.g ./codegen.py emoji.json | xclip -sel clip

7
scripts/update_emoji.md Normal file
View file

@ -0,0 +1,7 @@
# Updating emoji
1. Get the latest emoji-test.txt from here: https://unicode.org/Public/emoji/
2. Overwrite the existing resources/emoji-test.txt with the new one
3. Run `./scripts/emoji_codegen.py resources/emoji-test.txt` and replace the current tail of src/emoji/Provider.cpp with the new output
4. `make lint`
5. Compile and test

View file

@ -16,30 +16,37 @@
*/
#include <QBuffer>
#include <QPixmapCache>
#include <memory>
#include <unordered_map>
#include "AvatarProvider.h"
#include "Cache.h"
#include "Logging.h"
#include "MatrixClient.h"
static QPixmapCache avatar_cache;
namespace AvatarProvider {
void
resolve(const QString &room_id, const QString &user_id, QObject *receiver, AvatarCallback callback)
resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback callback)
{
const auto key = QString("%1 %2").arg(room_id).arg(user_id);
const auto avatarUrl = Cache::avatarUrl(room_id, user_id);
if (!Cache::AvatarUrls.contains(key) || !cache::client())
return;
const auto cacheKey = QString("%1_size_%2").arg(avatarUrl).arg(size);
if (avatarUrl.isEmpty())
return;
auto data = cache::client()->image(avatarUrl);
QPixmap pixmap;
if (avatar_cache.find(cacheKey, &pixmap)) {
callback(pixmap);
return;
}
auto data = cache::image(cacheKey);
if (!data.isNull()) {
callback(QImage::fromData(data));
pixmap.loadFromData(data);
avatar_cache.insert(cacheKey, pixmap);
callback(pixmap);
return;
}
@ -47,16 +54,22 @@ resolve(const QString &room_id, const QString &user_id, QObject *receiver, Avata
QObject::connect(proxy.get(),
&AvatarProxy::avatarDownloaded,
receiver,
[callback](const QByteArray &data) { callback(QImage::fromData(data)); });
[callback, cacheKey](const QByteArray &data) {
QPixmap pm;
pm.loadFromData(data);
avatar_cache.insert(cacheKey, pm);
callback(pm);
});
mtx::http::ThumbOpts opts;
opts.width = 256;
opts.height = 256;
opts.width = size;
opts.height = size;
opts.mxc_url = avatarUrl.toStdString();
http::client()->get_thumbnail(
opts,
[opts, proxy = std::move(proxy)](const std::string &res, mtx::http::RequestErr err) {
[opts, cacheKey, proxy = std::move(proxy)](const std::string &res,
mtx::http::RequestErr err) {
if (err) {
nhlog::net()->warn("failed to download avatar: {} - ({} {})",
opts.mxc_url,
@ -65,10 +78,21 @@ resolve(const QString &room_id, const QString &user_id, QObject *receiver, Avata
return;
}
cache::client()->saveImage(opts.mxc_url, res);
cache::saveImage(cacheKey.toStdString(), res);
auto data = QByteArray(res.data(), res.size());
emit proxy->avatarDownloaded(data);
emit proxy->avatarDownloaded(QByteArray(res.data(), res.size()));
});
}
void
resolve(const QString &room_id,
const QString &user_id,
int size,
QObject *receiver,
AvatarCallback callback)
{
const auto avatarUrl = cache::avatarUrl(room_id, user_id);
resolve(avatarUrl, size, receiver, callback);
}
}

View file

@ -17,7 +17,7 @@
#pragma once
#include <QImage>
#include <QPixmap>
#include <functional>
class AvatarProxy : public QObject
@ -28,9 +28,15 @@ signals:
void avatarDownloaded(const QByteArray &data);
};
using AvatarCallback = std::function<void(QImage)>;
using AvatarCallback = std::function<void(QPixmap)>;
namespace AvatarProvider {
void
resolve(const QString &room_id, const QString &user_id, QObject *receiver, AvatarCallback cb);
resolve(const QString &avatarUrl, int size, QObject *receiver, AvatarCallback cb);
void
resolve(const QString &room_id,
const QString &user_id,
int size,
QObject *receiver,
AvatarCallback cb);
}

File diff suppressed because it is too large Load diff

View file

@ -17,716 +17,280 @@
#pragma once
#include <boost/optional.hpp>
#include <QDateTime>
#include <QDir>
#include <QImage>
#include <QString>
#if __has_include(<lmdbxx/lmdb++.h>)
#include <lmdbxx/lmdb++.h>
#else
#include <lmdb++.h>
#include <mtx/events/join_rules.hpp>
#endif
#include <mtx/responses.hpp>
#include <mtxclient/crypto/client.hpp>
#include <mutex>
#include <nlohmann/json.hpp>
#include "Logging.h"
#include "CacheCryptoStructs.h"
#include "CacheStructs.h"
using mtx::events::state::JoinRule;
namespace cache {
void
init(const QString &user_id);
struct RoomMember
{
QString user_id;
QString display_name;
QImage avatar;
};
std::string
displayName(const std::string &room_id, const std::string &user_id);
QString
displayName(const QString &room_id, const QString &user_id);
QString
avatarUrl(const QString &room_id, const QString &user_id);
struct SearchResult
{
QString user_id;
QString display_name;
};
void
removeDisplayName(const QString &room_id, const QString &user_id);
void
removeAvatarUrl(const QString &room_id, const QString &user_id);
static int
numeric_key_comparison(const MDB_val *a, const MDB_val *b)
{
auto lhs = std::stoull(std::string((char *)a->mv_data, a->mv_size));
auto rhs = std::stoull(std::string((char *)b->mv_data, b->mv_size));
if (lhs < rhs)
return 1;
else if (lhs == rhs)
return 0;
return -1;
}
Q_DECLARE_METATYPE(SearchResult)
Q_DECLARE_METATYPE(QVector<SearchResult>)
Q_DECLARE_METATYPE(RoomMember)
Q_DECLARE_METATYPE(mtx::responses::Timeline)
//! Used to uniquely identify a list of read receipts.
struct ReadReceiptKey
{
std::string event_id;
std::string room_id;
};
inline void
to_json(json &j, const ReadReceiptKey &key)
{
j = json{{"event_id", key.event_id}, {"room_id", key.room_id}};
}
inline void
from_json(const json &j, ReadReceiptKey &key)
{
key.event_id = j.at("event_id").get<std::string>();
key.room_id = j.at("room_id").get<std::string>();
}
struct DescInfo
{
QString event_id;
QString username;
QString userid;
QString body;
QString timestamp;
QDateTime datetime;
};
//! UI info associated with a room.
struct RoomInfo
{
//! The calculated name of the room.
std::string name;
//! The topic of the room.
std::string topic;
//! The calculated avatar url of the room.
std::string avatar_url;
//! Whether or not the room is an invite.
bool is_invite = false;
//! Total number of members in the room.
int16_t member_count = 0;
//! Who can access to the room.
JoinRule join_rule = JoinRule::Public;
bool guest_access = false;
//! Metadata describing the last message in the timeline.
DescInfo msgInfo;
//! The list of tags associated with this room
std::vector<std::string> tags;
};
inline void
to_json(json &j, const RoomInfo &info)
{
j["name"] = info.name;
j["topic"] = info.topic;
j["avatar_url"] = info.avatar_url;
j["is_invite"] = info.is_invite;
j["join_rule"] = info.join_rule;
j["guest_access"] = info.guest_access;
if (info.member_count != 0)
j["member_count"] = info.member_count;
if (info.tags.size() != 0)
j["tags"] = info.tags;
}
inline void
from_json(const json &j, RoomInfo &info)
{
info.name = j.at("name");
info.topic = j.at("topic");
info.avatar_url = j.at("avatar_url");
info.is_invite = j.at("is_invite");
info.join_rule = j.at("join_rule");
info.guest_access = j.at("guest_access");
if (j.count("member_count"))
info.member_count = j.at("member_count");
if (j.count("tags"))
info.tags = j.at("tags").get<std::vector<std::string>>();
}
//! Basic information per member;
struct MemberInfo
{
std::string name;
std::string avatar_url;
};
inline void
to_json(json &j, const MemberInfo &info)
{
j["name"] = info.name;
j["avatar_url"] = info.avatar_url;
}
inline void
from_json(const json &j, MemberInfo &info)
{
info.name = j.at("name");
info.avatar_url = j.at("avatar_url");
}
struct RoomSearchResult
{
std::string room_id;
RoomInfo info;
QImage img;
};
Q_DECLARE_METATYPE(RoomSearchResult)
Q_DECLARE_METATYPE(RoomInfo)
// Extra information associated with an outbound megolm session.
struct OutboundGroupSessionData
{
std::string session_id;
std::string session_key;
uint64_t message_index = 0;
};
inline void
to_json(nlohmann::json &obj, const OutboundGroupSessionData &msg)
{
obj["session_id"] = msg.session_id;
obj["session_key"] = msg.session_key;
obj["message_index"] = msg.message_index;
}
inline void
from_json(const nlohmann::json &obj, OutboundGroupSessionData &msg)
{
msg.session_id = obj.at("session_id");
msg.session_key = obj.at("session_key");
msg.message_index = obj.at("message_index");
}
struct OutboundGroupSessionDataRef
{
OlmOutboundGroupSession *session;
OutboundGroupSessionData data;
};
struct DevicePublicKeys
{
std::string ed25519;
std::string curve25519;
};
inline void
to_json(nlohmann::json &obj, const DevicePublicKeys &msg)
{
obj["ed25519"] = msg.ed25519;
obj["curve25519"] = msg.curve25519;
}
inline void
from_json(const nlohmann::json &obj, DevicePublicKeys &msg)
{
msg.ed25519 = obj.at("ed25519");
msg.curve25519 = obj.at("curve25519");
}
//! Represents a unique megolm session identifier.
struct MegolmSessionIndex
{
//! The room in which this session exists.
std::string room_id;
//! The session_id of the megolm session.
std::string session_id;
//! The curve25519 public key of the sender.
std::string sender_key;
};
inline void
to_json(nlohmann::json &obj, const MegolmSessionIndex &msg)
{
obj["room_id"] = msg.room_id;
obj["session_id"] = msg.session_id;
obj["sender_key"] = msg.sender_key;
}
inline void
from_json(const nlohmann::json &obj, MegolmSessionIndex &msg)
{
msg.room_id = obj.at("room_id");
msg.session_id = obj.at("session_id");
msg.sender_key = obj.at("sender_key");
}
struct OlmSessionStorage
{
// Megolm sessions
std::map<std::string, mtx::crypto::InboundGroupSessionPtr> group_inbound_sessions;
std::map<std::string, mtx::crypto::OutboundGroupSessionPtr> group_outbound_sessions;
std::map<std::string, OutboundGroupSessionData> group_outbound_session_data;
// Guards for accessing megolm sessions.
std::mutex group_outbound_mtx;
std::mutex group_inbound_mtx;
};
class Cache : public QObject
{
Q_OBJECT
public:
Cache(const QString &userId, QObject *parent = nullptr);
static QHash<QString, QString> DisplayNames;
static QHash<QString, QString> AvatarUrls;
static QHash<QString, QString> UserColors;
static std::string displayName(const std::string &room_id, const std::string &user_id);
static QString displayName(const QString &room_id, const QString &user_id);
static QString avatarUrl(const QString &room_id, const QString &user_id);
static QString userColor(const QString &user_id);
static void removeDisplayName(const QString &room_id, const QString &user_id);
static void removeAvatarUrl(const QString &room_id, const QString &user_id);
static void removeUserColor(const QString &user_id);
static void insertDisplayName(const QString &room_id,
const QString &user_id,
const QString &display_name);
static void insertAvatarUrl(const QString &room_id,
const QString &user_id,
const QString &avatar_url);
static void insertUserColor(const QString &user_id, const QString &color_name);
static void clearUserColors();
void
insertDisplayName(const QString &room_id, const QString &user_id, const QString &display_name);
void
insertAvatarUrl(const QString &room_id, const QString &user_id, const QString &avatar_url);
//! Load saved data for the display names & avatars.
void populateMembers();
std::vector<std::string> joinedRooms();
void
populateMembers();
std::vector<std::string>
joinedRooms();
QMap<QString, RoomInfo> roomInfo(bool withInvites = true);
std::map<QString, bool> invites();
QMap<QString, RoomInfo>
roomInfo(bool withInvites = true);
std::map<QString, bool>
invites();
//! Calculate & return the name of the room.
QString getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
QString
getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
//! Get room join rules
JoinRule getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb);
bool getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb);
mtx::events::state::JoinRule
getRoomJoinRule(lmdb::txn &txn, lmdb::dbi &statesdb);
bool
getRoomGuestAccess(lmdb::txn &txn, lmdb::dbi &statesdb);
//! Retrieve the topic of the room if any.
QString getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
QString
getRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
//! Retrieve the room avatar's url if any.
QString getRoomAvatarUrl(lmdb::txn &txn,
lmdb::dbi &statesdb,
lmdb::dbi &membersdb,
const QString &room_id);
QString
getRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb, const QString &room_id);
//! Retrieve the version of the room if any.
QString
getRoomVersion(lmdb::txn &txn, lmdb::dbi &statesdb);
//! Retrieve member info from a room.
std::vector<RoomMember> getMembers(const std::string &room_id,
std::size_t startIndex = 0,
std::size_t len = 30);
std::vector<RoomMember>
getMembers(const std::string &room_id, std::size_t startIndex = 0, std::size_t len = 30);
void saveState(const mtx::responses::Sync &res);
bool isInitialized() const;
void
saveState(const mtx::responses::Sync &res);
bool
isInitialized();
std::string nextBatchToken() const;
std::string
nextBatchToken();
void deleteData();
void
deleteData();
void removeInvite(lmdb::txn &txn, const std::string &room_id);
void removeInvite(const std::string &room_id);
void removeRoom(lmdb::txn &txn, const std::string &roomid);
void removeRoom(const std::string &roomid);
void removeRoom(const QString &roomid) { removeRoom(roomid.toStdString()); };
void setup();
void
removeInvite(lmdb::txn &txn, const std::string &room_id);
void
removeInvite(const std::string &room_id);
void
removeRoom(lmdb::txn &txn, const std::string &roomid);
void
removeRoom(const std::string &roomid);
void
removeRoom(const QString &roomid);
void
setup();
bool isFormatValid();
void setCurrentFormat();
bool
isFormatValid();
void
setCurrentFormat();
std::map<QString, mtx::responses::Timeline> roomMessages();
std::map<QString, mtx::responses::Timeline>
roomMessages();
QMap<QString, mtx::responses::Notifications>
getTimelineMentions();
//! Retrieve all the user ids from a room.
std::vector<std::string> roomMembers(const std::string &room_id);
std::vector<std::string>
roomMembers(const std::string &room_id);
//! Check if the given user has power leve greater than than
//! lowest power level of the given events.
bool hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes,
bool
hasEnoughPowerLevel(const std::vector<mtx::events::EventType> &eventTypes,
const std::string &room_id,
const std::string &user_id);
//! Retrieves the saved room avatar.
QImage getRoomAvatar(const QString &id);
QImage getRoomAvatar(const std::string &id);
QImage
getRoomAvatar(const QString &id);
QImage
getRoomAvatar(const std::string &id);
//! Adds a user to the read list for the given event.
//!
//! There should be only one user id present in a receipt list per room.
//! The user id should be removed from any other lists.
using Receipts = std::map<std::string, std::map<std::string, uint64_t>>;
void updateReadReceipt(lmdb::txn &txn,
const std::string &room_id,
const Receipts &receipts);
void
updateReadReceipt(lmdb::txn &txn, const std::string &room_id, const Receipts &receipts);
//! Retrieve all the read receipts for the given event id and room.
//!
//! Returns a map of user ids and the time of the read receipt in milliseconds.
using UserReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
UserReceipts readReceipts(const QString &event_id, const QString &room_id);
UserReceipts
readReceipts(const QString &event_id, const QString &room_id);
//! Filter the events that have at least one read receipt.
std::vector<QString> filterReadEvents(const QString &room_id,
std::vector<QString>
filterReadEvents(const QString &room_id,
const std::vector<QString> &event_ids,
const std::string &excluded_user);
//! Add event for which we are expecting some read receipts.
void addPendingReceipt(const QString &room_id, const QString &event_id);
void removePendingReceipt(lmdb::txn &txn,
const std::string &room_id,
const std::string &event_id);
void notifyForReadReceipts(const std::string &room_id);
std::vector<QString> pendingReceiptsEvents(lmdb::txn &txn, const std::string &room_id);
void
addPendingReceipt(const QString &room_id, const QString &event_id);
void
removePendingReceipt(lmdb::txn &txn, const std::string &room_id, const std::string &event_id);
void
notifyForReadReceipts(const std::string &room_id);
std::vector<QString>
pendingReceiptsEvents(lmdb::txn &txn, const std::string &room_id);
QByteArray image(const QString &url) const;
QByteArray image(lmdb::txn &txn, const std::string &url) const;
QByteArray image(const std::string &url) const
QByteArray
image(const QString &url);
QByteArray
image(lmdb::txn &txn, const std::string &url);
inline QByteArray
image(const std::string &url)
{
return image(QString::fromStdString(url));
}
void saveImage(const std::string &url, const std::string &data);
void saveImage(const QString &url, const QByteArray &data);
void
saveImage(const std::string &url, const std::string &data);
void
saveImage(const QString &url, const QByteArray &data);
RoomInfo singleRoomInfo(const std::string &room_id);
std::vector<std::string> roomsWithStateUpdates(const mtx::responses::Sync &res);
std::vector<std::string> roomsWithTagUpdates(const mtx::responses::Sync &res);
std::map<QString, RoomInfo> getRoomInfo(const std::vector<std::string> &rooms);
std::map<QString, RoomInfo> roomUpdates(const mtx::responses::Sync &sync)
RoomInfo
singleRoomInfo(const std::string &room_id);
std::vector<std::string>
roomsWithStateUpdates(const mtx::responses::Sync &res);
std::vector<std::string>
roomsWithTagUpdates(const mtx::responses::Sync &res);
std::map<QString, RoomInfo>
getRoomInfo(const std::vector<std::string> &rooms);
inline std::map<QString, RoomInfo>
roomUpdates(const mtx::responses::Sync &sync)
{
return getRoomInfo(roomsWithStateUpdates(sync));
}
std::map<QString, RoomInfo> roomTagUpdates(const mtx::responses::Sync &sync)
inline std::map<QString, RoomInfo>
roomTagUpdates(const mtx::responses::Sync &sync)
{
return getRoomInfo(roomsWithTagUpdates(sync));
}
//! Calculates which the read status of a room.
//! Whether all the events in the timeline have been read.
bool calculateRoomReadStatus(const std::string &room_id);
void calculateRoomReadStatus();
bool
calculateRoomReadStatus(const std::string &room_id);
void
calculateRoomReadStatus();
QVector<SearchResult> searchUsers(const std::string &room_id,
const std::string &query,
std::uint8_t max_items = 5);
std::vector<RoomSearchResult> searchRooms(const std::string &query,
std::uint8_t max_items = 5);
std::vector<SearchResult>
searchUsers(const std::string &room_id, const std::string &query, std::uint8_t max_items = 5);
std::vector<RoomSearchResult>
searchRooms(const std::string &query, std::uint8_t max_items = 5);
void markSentNotification(const std::string &event_id);
void
markSentNotification(const std::string &event_id);
//! Removes an event from the sent notifications.
void removeReadNotification(const std::string &event_id);
void
removeReadNotification(const std::string &event_id);
//! Check if we have sent a desktop notification for the given event id.
bool isNotificationSent(const std::string &event_id);
bool
isNotificationSent(const std::string &event_id);
//! Add all notifications containing a user mention to the db.
void
saveTimelineMentions(const mtx::responses::Notifications &res);
//! Remove old unused data.
void deleteOldMessages();
void deleteOldData() noexcept;
void
deleteOldMessages();
void
deleteOldData() noexcept;
//! Retrieve all saved room ids.
std::vector<std::string> getRoomIds(lmdb::txn &txn);
std::vector<std::string>
getRoomIds(lmdb::txn &txn);
//! Mark a room that uses e2e encryption.
void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
bool isRoomEncrypted(const std::string &room_id);
//! Save the public keys for a device.
void saveDeviceKeys(const std::string &device_id);
void getDeviceKeys(const std::string &device_id);
//! Save the device list for a user.
void setDeviceList(const std::string &user_id, const std::vector<std::string> &devices);
std::vector<std::string> getDeviceList(const std::string &user_id);
void
setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
bool
isRoomEncrypted(const std::string &room_id);
//! Check if a user is a member of the room.
bool isRoomMember(const std::string &user_id, const std::string &room_id);
bool
isRoomMember(const std::string &user_id, const std::string &room_id);
//
// Outbound Megolm Sessions
//
void saveOutboundMegolmSession(const std::string &room_id,
void
saveOutboundMegolmSession(const std::string &room_id,
const OutboundGroupSessionData &data,
mtx::crypto::OutboundGroupSessionPtr session);
OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id);
bool outboundMegolmSessionExists(const std::string &room_id) noexcept;
void updateOutboundMegolmSession(const std::string &room_id, int message_index);
OutboundGroupSessionDataRef
getOutboundMegolmSession(const std::string &room_id);
bool
outboundMegolmSessionExists(const std::string &room_id) noexcept;
void
updateOutboundMegolmSession(const std::string &room_id, int message_index);
void importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys);
mtx::crypto::ExportedSessionKeys exportSessionKeys();
void
importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys);
mtx::crypto::ExportedSessionKeys
exportSessionKeys();
//
// Inbound Megolm Sessions
//
void saveInboundMegolmSession(const MegolmSessionIndex &index,
void
saveInboundMegolmSession(const MegolmSessionIndex &index,
mtx::crypto::InboundGroupSessionPtr session);
OlmInboundGroupSession *getInboundMegolmSession(const MegolmSessionIndex &index);
bool inboundMegolmSessionExists(const MegolmSessionIndex &index);
OlmInboundGroupSession *
getInboundMegolmSession(const MegolmSessionIndex &index);
bool
inboundMegolmSessionExists(const MegolmSessionIndex &index);
//
// Olm Sessions
//
void saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session);
std::vector<std::string> getOlmSessions(const std::string &curve25519);
boost::optional<mtx::crypto::OlmSessionPtr> getOlmSession(const std::string &curve25519,
const std::string &session_id);
void saveOlmAccount(const std::string &pickled);
std::string restoreOlmAccount();
void restoreSessions();
OlmSessionStorage session_storage;
signals:
void newReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
void roomReadStatus(const std::map<QString, bool> &status);
private:
//! Save an invited room.
void saveInvite(lmdb::txn &txn,
lmdb::dbi &statesdb,
lmdb::dbi &membersdb,
const mtx::responses::InvitedRoom &room);
QString getInviteRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
QString getInviteRoomTopic(lmdb::txn &txn, lmdb::dbi &statesdb);
QString getInviteRoomAvatarUrl(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
DescInfo getLastMessageInfo(lmdb::txn &txn, const std::string &room_id);
void saveTimelineMessages(lmdb::txn &txn,
const std::string &room_id,
const mtx::responses::Timeline &res);
mtx::responses::Timeline getTimelineMessages(lmdb::txn &txn, const std::string &room_id);
//! Remove a room from the cache.
// void removeLeftRoom(lmdb::txn &txn, const std::string &room_id);
template<class T>
void saveStateEvents(lmdb::txn &txn,
const lmdb::dbi &statesdb,
const lmdb::dbi &membersdb,
const std::string &room_id,
const std::vector<T> &events)
{
for (const auto &e : events)
saveStateEvent(txn, statesdb, membersdb, room_id, e);
}
template<class T>
void saveStateEvent(lmdb::txn &txn,
const lmdb::dbi &statesdb,
const lmdb::dbi &membersdb,
const std::string &room_id,
const T &event)
{
using namespace mtx::events;
using namespace mtx::events::state;
if (boost::get<StateEvent<Member>>(&event) != nullptr) {
const auto e = boost::get<StateEvent<Member>>(event);
switch (e.content.membership) {
//
// We only keep users with invite or join membership.
//
case Membership::Invite:
case Membership::Join: {
auto display_name = e.content.display_name.empty()
? e.state_key
: e.content.display_name;
// Lightweight representation of a member.
MemberInfo tmp{display_name, e.content.avatar_url};
lmdb::dbi_put(txn,
membersdb,
lmdb::val(e.state_key),
lmdb::val(json(tmp).dump()));
insertDisplayName(QString::fromStdString(room_id),
QString::fromStdString(e.state_key),
QString::fromStdString(display_name));
insertAvatarUrl(QString::fromStdString(room_id),
QString::fromStdString(e.state_key),
QString::fromStdString(e.content.avatar_url));
break;
}
default: {
lmdb::dbi_del(
txn, membersdb, lmdb::val(e.state_key), lmdb::val(""));
removeDisplayName(QString::fromStdString(room_id),
QString::fromStdString(e.state_key));
removeAvatarUrl(QString::fromStdString(room_id),
QString::fromStdString(e.state_key));
break;
}
}
return;
} else if (boost::get<StateEvent<Encryption>>(&event) != nullptr) {
setEncryptedRoom(txn, room_id);
return;
}
if (!isStateEvent(event))
return;
boost::apply_visitor(
[&txn, &statesdb](auto e) {
lmdb::dbi_put(
txn, statesdb, lmdb::val(to_string(e.type)), lmdb::val(json(e).dump()));
},
event);
}
template<class T>
bool isStateEvent(const T &e)
{
using namespace mtx::events;
using namespace mtx::events::state;
return boost::get<StateEvent<Aliases>>(&e) != nullptr ||
boost::get<StateEvent<state::Avatar>>(&e) != nullptr ||
boost::get<StateEvent<CanonicalAlias>>(&e) != nullptr ||
boost::get<StateEvent<Create>>(&e) != nullptr ||
boost::get<StateEvent<GuestAccess>>(&e) != nullptr ||
boost::get<StateEvent<HistoryVisibility>>(&e) != nullptr ||
boost::get<StateEvent<JoinRules>>(&e) != nullptr ||
boost::get<StateEvent<Name>>(&e) != nullptr ||
boost::get<StateEvent<Member>>(&e) != nullptr ||
boost::get<StateEvent<PowerLevels>>(&e) != nullptr ||
boost::get<StateEvent<Topic>>(&e) != nullptr;
}
template<class T>
bool containsStateUpdates(const T &e)
{
using namespace mtx::events;
using namespace mtx::events::state;
return boost::get<StateEvent<state::Avatar>>(&e) != nullptr ||
boost::get<StateEvent<CanonicalAlias>>(&e) != nullptr ||
boost::get<StateEvent<Name>>(&e) != nullptr ||
boost::get<StateEvent<Member>>(&e) != nullptr ||
boost::get<StateEvent<Topic>>(&e) != nullptr;
}
bool containsStateUpdates(const mtx::events::collections::StrippedEvents &e)
{
using namespace mtx::events;
using namespace mtx::events::state;
return boost::get<StrippedEvent<state::Avatar>>(&e) != nullptr ||
boost::get<StrippedEvent<CanonicalAlias>>(&e) != nullptr ||
boost::get<StrippedEvent<Name>>(&e) != nullptr ||
boost::get<StrippedEvent<Member>>(&e) != nullptr ||
boost::get<StrippedEvent<Topic>>(&e) != nullptr;
}
void saveInvites(lmdb::txn &txn,
const std::map<std::string, mtx::responses::InvitedRoom> &rooms);
//! Sends signals for the rooms that are removed.
void removeLeftRooms(lmdb::txn &txn,
const std::map<std::string, mtx::responses::LeftRoom> &rooms)
{
for (const auto &room : rooms) {
removeRoom(txn, room.first);
// Clean up leftover invites.
removeInvite(txn, room.first);
}
}
lmdb::dbi getPendingReceiptsDb(lmdb::txn &txn)
{
return lmdb::dbi::open(txn, "pending_receipts", MDB_CREATE);
}
lmdb::dbi getMessagesDb(lmdb::txn &txn, const std::string &room_id)
{
auto db =
lmdb::dbi::open(txn, std::string(room_id + "/messages").c_str(), MDB_CREATE);
lmdb::dbi_set_compare(txn, db, numeric_key_comparison);
return db;
}
lmdb::dbi getInviteStatesDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(
txn, std::string(room_id + "/invite_state").c_str(), MDB_CREATE);
}
lmdb::dbi getInviteMembersDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(
txn, std::string(room_id + "/invite_members").c_str(), MDB_CREATE);
}
lmdb::dbi getStatesDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(txn, std::string(room_id + "/state").c_str(), MDB_CREATE);
}
lmdb::dbi getMembersDb(lmdb::txn &txn, const std::string &room_id)
{
return lmdb::dbi::open(txn, std::string(room_id + "/members").c_str(), MDB_CREATE);
}
//! Retrieves or creates the database that stores the open OLM sessions between our device
//! and the given curve25519 key which represents another device.
//!
//! Each entry is a map from the session_id to the pickled representation of the session.
lmdb::dbi getOlmSessionsDb(lmdb::txn &txn, const std::string &curve25519_key)
{
return lmdb::dbi::open(
txn, std::string("olm_sessions/" + curve25519_key).c_str(), MDB_CREATE);
}
QString getDisplayName(const mtx::events::StateEvent<mtx::events::state::Member> &event)
{
if (!event.content.display_name.empty())
return QString::fromStdString(event.content.display_name);
return QString::fromStdString(event.state_key);
}
void setNextBatchToken(lmdb::txn &txn, const std::string &token);
void setNextBatchToken(lmdb::txn &txn, const QString &token);
lmdb::env env_;
lmdb::dbi syncStateDb_;
lmdb::dbi roomsDb_;
lmdb::dbi invitesDb_;
lmdb::dbi mediaDb_;
lmdb::dbi readReceiptsDb_;
lmdb::dbi notificationsDb_;
lmdb::dbi devicesDb_;
lmdb::dbi deviceKeysDb_;
lmdb::dbi inboundMegolmSessionDb_;
lmdb::dbi outboundMegolmSessionDb_;
QString localUserId_;
QString cacheDirectory_;
};
namespace cache {
void
init(const QString &user_id);
saveOlmSession(const std::string &curve25519, mtx::crypto::OlmSessionPtr session);
std::vector<std::string>
getOlmSessions(const std::string &curve25519);
std::optional<mtx::crypto::OlmSessionPtr>
getOlmSession(const std::string &curve25519, const std::string &session_id);
Cache *
client();
void
saveOlmAccount(const std::string &pickled);
std::string
restoreOlmAccount();
void
restoreSessions();
}

67
src/CacheCryptoStructs.h Normal file
View file

@ -0,0 +1,67 @@
#pragma once
#include <map>
#include <mutex>
//#include <nlohmann/json.hpp>
#include <mtx/responses.hpp>
#include <mtxclient/crypto/client.hpp>
// Extra information associated with an outbound megolm session.
struct OutboundGroupSessionData
{
std::string session_id;
std::string session_key;
uint64_t message_index = 0;
};
void
to_json(nlohmann::json &obj, const OutboundGroupSessionData &msg);
void
from_json(const nlohmann::json &obj, OutboundGroupSessionData &msg);
struct OutboundGroupSessionDataRef
{
OlmOutboundGroupSession *session;
OutboundGroupSessionData data;
};
struct DevicePublicKeys
{
std::string ed25519;
std::string curve25519;
};
void
to_json(nlohmann::json &obj, const DevicePublicKeys &msg);
void
from_json(const nlohmann::json &obj, DevicePublicKeys &msg);
//! Represents a unique megolm session identifier.
struct MegolmSessionIndex
{
//! The room in which this session exists.
std::string room_id;
//! The session_id of the megolm session.
std::string session_id;
//! The curve25519 public key of the sender.
std::string sender_key;
};
void
to_json(nlohmann::json &obj, const MegolmSessionIndex &msg);
void
from_json(const nlohmann::json &obj, MegolmSessionIndex &msg);
struct OlmSessionStorage
{
// Megolm sessions
std::map<std::string, mtx::crypto::InboundGroupSessionPtr> group_inbound_sessions;
std::map<std::string, mtx::crypto::OutboundGroupSessionPtr> group_outbound_sessions;
std::map<std::string, OutboundGroupSessionData> group_outbound_session_data;
// Guards for accessing megolm sessions.
std::mutex group_outbound_mtx;
std::mutex group_inbound_mtx;
};

Some files were not shown because too many files have changed in this diff Show more