mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 19:08:58 +03:00
Merge remote-tracking branch 'nheko-im/master' into video_player_enhancements
This commit is contained in:
commit
093f9f9e33
39 changed files with 795 additions and 295 deletions
|
@ -6,7 +6,7 @@ set -eux
|
||||||
#TAG=$(git tag -l --points-at HEAD)
|
#TAG=$(git tag -l --points-at HEAD)
|
||||||
|
|
||||||
# Add Qt binaries to path
|
# Add Qt binaries to path
|
||||||
PATH=/usr/local/opt/qt/bin/:${PATH}
|
PATH=/usr/local/opt/qt@5/bin/:${PATH}
|
||||||
|
|
||||||
( cd build
|
( cd build
|
||||||
# macdeployqt does not copy symlinks over.
|
# macdeployqt does not copy symlinks over.
|
||||||
|
|
61
.github/ISSUE_TEMPLATE/bug_report.md
vendored
61
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -1,61 +0,0 @@
|
||||||
---
|
|
||||||
name: Bug report
|
|
||||||
about: Create a report to help us improve
|
|
||||||
title: ''
|
|
||||||
labels: bug
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Describe the bug
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
### To Reproduce
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
### Expected behavior
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
### Screenshots
|
|
||||||
If applicable, add screenshots to help explain your problem.
|
|
||||||
|
|
||||||
### System:
|
|
||||||
|
|
||||||
- Nheko version: <!-- Get the version from the settings menu (bottom left corner) -->
|
|
||||||
- Profile used: <!-- If you are not using the default profile, mention it here -->
|
|
||||||
- Installation method: <!-- AppImage, some repository, local build etc -->
|
|
||||||
- Operating System:
|
|
||||||
- Qt version: <!-- If you compiled it yourself -->
|
|
||||||
- C++ compiler: <!-- if you compiled it yourself -->
|
|
||||||
- Desktop Environment: <!-- for Linux -->
|
|
||||||
|
|
||||||
### Logs
|
|
||||||
<!-- If applicable -->
|
|
||||||
|
|
||||||
<!-- The log file is located in
|
|
||||||
Linux: ~/.cache/nheko/
|
|
||||||
macOS: ~/Library/Caches/nheko or /Library/Caches/nheko
|
|
||||||
Windows: C:/Users/<USER>/AppData/Local/nheko/cache
|
|
||||||
-->
|
|
||||||
|
|
||||||
### Debugger backtrace
|
|
||||||
<!--
|
|
||||||
If the program crashed send a backtrace:
|
|
||||||
|
|
||||||
You can retrieve a backtrace by building nheko with -DCMAKE_BUILD_TYPE=Debug
|
|
||||||
and running it through gdb or lldb.
|
|
||||||
|
|
||||||
gdb ./build/nheko
|
|
||||||
|
|
||||||
>> run
|
|
||||||
|
|
||||||
... Make the program crash
|
|
||||||
|
|
||||||
>> bt
|
|
||||||
|
|
||||||
... Paste a link of the output below (Use a pastebin, don't paste directly in the github issue).
|
|
||||||
-->
|
|
150
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
150
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
name: Bug Report
|
||||||
|
description: Create a report to help us improve
|
||||||
|
#title: "[Bug]: "
|
||||||
|
labels: [bug]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this bug report!
|
||||||
|
Please try to fill out all fields to the best of your ability.
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Describe the bug
|
||||||
|
description: A clear and concise description of what the bug is.
|
||||||
|
placeholder: Enter your description here.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: reproduction-steps
|
||||||
|
attributes:
|
||||||
|
label: To Reproduce
|
||||||
|
description: Steps to reproduce the behavior.
|
||||||
|
placeholder: |
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
value: |
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: behaviour
|
||||||
|
attributes:
|
||||||
|
label: What happened?
|
||||||
|
description: A clear and concise description of what actually happened.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: expected-behaviour
|
||||||
|
attributes:
|
||||||
|
label: Expected behavior
|
||||||
|
description: A clear and concise description of what you expected to happen.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: screenshots
|
||||||
|
attributes:
|
||||||
|
label: Screenshots
|
||||||
|
description: If applicable, add screenshots to help explain your problem.
|
||||||
|
placeholder: Upload your screenshots here. You can paste them or click on "Attach files".
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Version
|
||||||
|
description: Get the version from the settings menu (bottom left corner)
|
||||||
|
placeholder: 0.0.1-deafbeef
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: os
|
||||||
|
attributes:
|
||||||
|
label: Operating system
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- Linux
|
||||||
|
- macOS
|
||||||
|
- Windows
|
||||||
|
- BSD
|
||||||
|
- Haiku
|
||||||
|
- Other
|
||||||
|
- type: dropdown
|
||||||
|
id: install-method
|
||||||
|
attributes:
|
||||||
|
label: Installation method
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- Flathub
|
||||||
|
- Flatpak nightly repo or download
|
||||||
|
- AppImage
|
||||||
|
- Windows download
|
||||||
|
- macOS DMG file
|
||||||
|
- Some repository (AUR, homebrew, distribution repository, PPA, etc)
|
||||||
|
- Local build
|
||||||
|
- type: input
|
||||||
|
id: qt-version
|
||||||
|
attributes:
|
||||||
|
label: Qt version
|
||||||
|
description: What version of Qt does your system use? (If you compiled Nheko yourself.)
|
||||||
|
placeholder: 5.15.2.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: input
|
||||||
|
id: compiler
|
||||||
|
attributes:
|
||||||
|
label: C++ compiler
|
||||||
|
description: What compiler (and version) did you use (if you compiled Nheko yourself)?
|
||||||
|
placeholder: gcc-9000
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: input
|
||||||
|
id: de
|
||||||
|
attributes:
|
||||||
|
label: Desktop Environment
|
||||||
|
description: If you are on Linux, describe your desktop environment.
|
||||||
|
placeholder: KDE with i3 as the window manager
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: checkboxes
|
||||||
|
id: profiles
|
||||||
|
attributes:
|
||||||
|
label: Did you use profiles?
|
||||||
|
description: Usually by passing the --profile command line parameter. If you don't know, answer 'no'.
|
||||||
|
options:
|
||||||
|
- label: Profiles used?
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Relevant log output
|
||||||
|
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||||
|
placeholder: |
|
||||||
|
The log file is located in
|
||||||
|
Linux: ~/.cache/nheko/
|
||||||
|
macOS: ~/Library/Caches/nheko or /Library/Caches/nheko
|
||||||
|
Windows: C:/Users/<USER>/AppData/Local/nheko/cache
|
||||||
|
render: shell
|
||||||
|
- type: textarea
|
||||||
|
id: backtrace
|
||||||
|
attributes:
|
||||||
|
label: Backtrace
|
||||||
|
description: If the program crashed send a backtrace.
|
||||||
|
placeholder: |
|
||||||
|
You can retrieve a backtrace by building nheko with -DCMAKE_BUILD_TYPE=Debug and running it through gdb or lldb.
|
||||||
|
|
||||||
|
gdb ./build/nheko
|
||||||
|
|
||||||
|
>> run
|
||||||
|
|
||||||
|
... Make the program crash
|
||||||
|
|
||||||
|
>> bt
|
||||||
|
render: shell
|
||||||
|
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -1,20 +0,0 @@
|
||||||
---
|
|
||||||
name: Feature request
|
|
||||||
about: Suggest an idea for this project
|
|
||||||
title: ''
|
|
||||||
labels: enhancement
|
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
|
||||||
A clear and concise description of what you want to happen.
|
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context or screenshots about the feature request here.
|
|
49
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
49
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
name: Feature request
|
||||||
|
description: Suggest an idea for this project
|
||||||
|
labels: [enhancement]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Please verify that there is no feature request for this already!
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: The Problem
|
||||||
|
description: Is your feature request related to a problem? Please describe.
|
||||||
|
placeholder: "A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: solution
|
||||||
|
attributes:
|
||||||
|
label: The Solution
|
||||||
|
description: Describe the solution you'd like
|
||||||
|
placeholder: A clear and concise description of what you want to happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: Alternatives
|
||||||
|
description: Describe alternatives you've considered.
|
||||||
|
placeholder: A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: context
|
||||||
|
attributes:
|
||||||
|
label: Additional context
|
||||||
|
description: Describe alternatives you've considered.
|
||||||
|
placeholder: Add any other context or screenshots about the feature request here.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: checkboxes
|
||||||
|
id: version-check
|
||||||
|
attributes:
|
||||||
|
label: Happens in the latest version
|
||||||
|
description: Please verify that this is still missing in the latest version.
|
||||||
|
options:
|
||||||
|
- label: Yes, this feature is still missing.
|
||||||
|
required: true
|
||||||
|
|
|
@ -381,7 +381,7 @@ if(USE_BUNDLED_MTXCLIENT)
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
MatrixClient
|
MatrixClient
|
||||||
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
|
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
|
||||||
GIT_TAG bcf363cb5e6c423f40c96123e227bc8c5f6d6f80
|
GIT_TAG deb51ef1d6df870098069312f0a1999550e1eb85
|
||||||
)
|
)
|
||||||
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
|
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
|
||||||
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
|
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
|
||||||
|
@ -676,7 +676,7 @@ if(USE_BUNDLED_COEURL)
|
||||||
FetchContent_Declare(
|
FetchContent_Declare(
|
||||||
coeurl
|
coeurl
|
||||||
GIT_REPOSITORY https://nheko.im/Nheko-Reborn/coeurl.git
|
GIT_REPOSITORY https://nheko.im/Nheko-Reborn/coeurl.git
|
||||||
GIT_TAG e9010d1ce14e7163d1cb5407ed27b23303781796
|
GIT_TAG 3901507db25cf3f9364b58cd8c7880640900c992
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(coeurl)
|
FetchContent_MakeAvailable(coeurl)
|
||||||
target_link_libraries(nheko PUBLIC coeurl::coeurl)
|
target_link_libraries(nheko PUBLIC coeurl::coeurl)
|
||||||
|
|
|
@ -213,7 +213,7 @@ sudo emerge -a ">=dev-qt/qtgui-5.10.0" media-libs/fontconfig dev-libs/qtkeychain
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build requirements + qml modules needed at runtime (you may not need all of them, but the following seem to work according to reports):
|
# Build requirements + qml modules needed at runtime (you may not need all of them, but the following seem to work according to reports):
|
||||||
sudo apt install g++ cmake zlib1g-dev libssl-dev qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt5svg5-dev libboost-system-dev libboost-thread-dev libboost-iostreams-dev libolm-dev liblmdb++-dev libcmark-dev nlohmann-json3-dev libspdlog-dev libgtest-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,-labs-platform,graphicaleffects,quick-controls2} qt5keychain-dev
|
sudo apt install g++ cmake zlib1g-dev libssl-dev qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt5svg5-dev libboost-system-dev libboost-thread-dev libboost-iostreams-dev libolm-dev liblmdb++-dev libcmark-dev nlohmann-json3-dev libspdlog-dev libgtest-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,-labs-platform,graphicaleffects,quick-controls2} qt5keychain-dev libevent-dev libcurl-dev
|
||||||
```
|
```
|
||||||
This will install all dependencies, except for tweeny (use bundled tweeny)
|
This will install all dependencies, except for tweeny (use bundled tweeny)
|
||||||
and mtxclient (needs to be build separately).
|
and mtxclient (needs to be build separately).
|
||||||
|
|
|
@ -152,7 +152,7 @@ modules:
|
||||||
- -Ddefault_library=static
|
- -Ddefault_library=static
|
||||||
name: coeurl
|
name: coeurl
|
||||||
sources:
|
sources:
|
||||||
- commit: 417821a07cfe4429b08a2efed5e480a498087afd
|
- commit: 3901507db25cf3f9364b58cd8c7880640900c992
|
||||||
type: git
|
type: git
|
||||||
url: https://nheko.im/nheko-reborn/coeurl.git
|
url: https://nheko.im/nheko-reborn/coeurl.git
|
||||||
- config-opts:
|
- config-opts:
|
||||||
|
@ -163,7 +163,7 @@ modules:
|
||||||
buildsystem: cmake-ninja
|
buildsystem: cmake-ninja
|
||||||
name: mtxclient
|
name: mtxclient
|
||||||
sources:
|
sources:
|
||||||
- commit: bcf363cb5e6c423f40c96123e227bc8c5f6d6f80
|
- commit: deb51ef1d6df870098069312f0a1999550e1eb85
|
||||||
type: git
|
type: git
|
||||||
url: https://github.com/Nheko-Reborn/mtxclient.git
|
url: https://github.com/Nheko-Reborn/mtxclient.git
|
||||||
- config-opts:
|
- config-opts:
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import "./ui"
|
import "./ui"
|
||||||
import QtGraphicalEffects 1.0
|
|
||||||
import QtQuick 2.6
|
import QtQuick 2.6
|
||||||
import QtQuick.Controls 2.3
|
import QtQuick.Controls 2.3
|
||||||
import im.nheko 1.0
|
import im.nheko 1.0
|
||||||
|
@ -21,7 +20,7 @@ Rectangle {
|
||||||
|
|
||||||
width: 48
|
width: 48
|
||||||
height: 48
|
height: 48
|
||||||
radius: Settings.avatarCircles ? height / 2 : 3
|
radius: Settings.avatarCircles ? height / 2 : height / 8
|
||||||
color: Nheko.colors.alternateBase
|
color: Nheko.colors.alternateBase
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
mouseArea.clicked.connect(clicked);
|
mouseArea.clicked.connect(clicked);
|
||||||
|
@ -50,8 +49,7 @@ Rectangle {
|
||||||
smooth: true
|
smooth: true
|
||||||
sourceSize.width: avatar.width
|
sourceSize.width: avatar.width
|
||||||
sourceSize.height: avatar.height
|
sourceSize.height: avatar.height
|
||||||
layer.enabled: true
|
source: avatar.url ? (avatar.url + "?radius=" + (Settings.avatarCircles ? 100.0 : 25.0) + ((avatar.crop) ? "" : "&scale")) : ""
|
||||||
source: avatar.url + ((avatar.crop || !avatar.url) ? "" : "?scale")
|
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: mouseArea
|
id: mouseArea
|
||||||
|
@ -65,18 +63,6 @@ Rectangle {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
layer.effect: OpacityMask {
|
|
||||||
cached: true
|
|
||||||
|
|
||||||
maskSource: Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
width: avatar.width
|
|
||||||
height: avatar.height
|
|
||||||
radius: Settings.avatarCircles ? height / 2 : 3
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
@ -85,7 +71,7 @@ Rectangle {
|
||||||
visible: !!userid
|
visible: !!userid
|
||||||
height: avatar.height / 6
|
height: avatar.height / 6
|
||||||
width: height
|
width: height
|
||||||
radius: Settings.avatarCircles ? height / 2 : height / 4
|
radius: Settings.avatarCircles ? height / 2 : height / 8
|
||||||
color: {
|
color: {
|
||||||
switch (TimelineManager.userPresence(userid)) {
|
switch (TimelineManager.userPresence(userid)) {
|
||||||
case "online":
|
case "online":
|
||||||
|
|
|
@ -68,6 +68,7 @@ Popup {
|
||||||
isOnlyEmoji: modelData.isOnlyEmoji ?? false
|
isOnlyEmoji: modelData.isOnlyEmoji ?? false
|
||||||
userId: modelData.userId ?? ""
|
userId: modelData.userId ?? ""
|
||||||
userName: modelData.userName ?? ""
|
userName: modelData.userName ?? ""
|
||||||
|
encryptionError: modelData.encryptionError ?? ""
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixTextField {
|
MatrixTextField {
|
||||||
|
@ -85,6 +86,9 @@ Popup {
|
||||||
} else if (event.key == Qt.Key_Down && completerPopup.opened) {
|
} else if (event.key == Qt.Key_Down && completerPopup.opened) {
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
completerPopup.down();
|
completerPopup.down();
|
||||||
|
} else if (event.key == Qt.Key_Tab && completerPopup.opened) {
|
||||||
|
event.accepted = true;
|
||||||
|
completerPopup.down();
|
||||||
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
|
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
|
||||||
completerPopup.finishCompletion();
|
completerPopup.finishCompletion();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
|
|
|
@ -6,7 +6,6 @@ import "./delegates"
|
||||||
import "./emoji"
|
import "./emoji"
|
||||||
import "./ui"
|
import "./ui"
|
||||||
import Qt.labs.platform 1.1 as Platform
|
import Qt.labs.platform 1.1 as Platform
|
||||||
import QtGraphicalEffects 1.0
|
|
||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.15
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick.Layouts 1.2
|
import QtQuick.Layouts 1.2
|
||||||
|
|
|
@ -45,6 +45,9 @@ Popup {
|
||||||
} else if (event.key == Qt.Key_Down && completerPopup.opened) {
|
} else if (event.key == Qt.Key_Down && completerPopup.opened) {
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
completerPopup.down();
|
completerPopup.down();
|
||||||
|
} else if (event.key == Qt.Key_Tab && completerPopup.opened) {
|
||||||
|
event.accepted = true;
|
||||||
|
completerPopup.down();
|
||||||
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
|
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
|
||||||
completerPopup.finishCompletion();
|
completerPopup.finishCompletion();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
|
|
|
@ -45,6 +45,7 @@ Rectangle {
|
||||||
isOnlyEmoji: modelData.isOnlyEmoji ?? false
|
isOnlyEmoji: modelData.isOnlyEmoji ?? false
|
||||||
userId: modelData.userId ?? ""
|
userId: modelData.userId ?? ""
|
||||||
userName: modelData.userName ?? ""
|
userName: modelData.userName ?? ""
|
||||||
|
encryptionError: modelData.encryptionError ?? ""
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
|
|
|
@ -33,8 +33,9 @@ Page {
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onCurrentRoomChanged() {
|
function onCurrentRoomChanged() {
|
||||||
roomlist.positionViewAtIndex(Rooms.roomidToIndex(Rooms.currentRoom.roomId), ListView.Contain);
|
if (Rooms.currentRoom)
|
||||||
console.log("Test" + Rooms.currentRoom.roomId + " " + Rooms.roomidToIndex(Rooms.currentRoom.roomId));
|
roomlist.positionViewAtIndex(Rooms.roomidToIndex(Rooms.currentRoom.roomId), ListView.Contain);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
target: Rooms
|
target: Rooms
|
||||||
|
@ -190,7 +191,12 @@ Page {
|
||||||
|
|
||||||
TapHandler {
|
TapHandler {
|
||||||
margin: -Nheko.paddingSmall
|
margin: -Nheko.paddingSmall
|
||||||
onSingleTapped: Rooms.setCurrentRoom(roomId)
|
onSingleTapped: {
|
||||||
|
if (!Rooms.currentRoom || Rooms.currentRoom.roomId !== roomId)
|
||||||
|
Rooms.setCurrentRoom(roomId);
|
||||||
|
else
|
||||||
|
Rooms.resetCurrentRoom();
|
||||||
|
}
|
||||||
onLongPressed: {
|
onLongPressed: {
|
||||||
if (!isInvite)
|
if (!isInvite)
|
||||||
roomContextMenu.show(roomId, tags);
|
roomContextMenu.show(roomId, tags);
|
||||||
|
|
|
@ -13,6 +13,7 @@ ApplicationWindow {
|
||||||
id: roomMembersRoot
|
id: roomMembersRoot
|
||||||
|
|
||||||
property MemberList members
|
property MemberList members
|
||||||
|
property Room room
|
||||||
|
|
||||||
title: qsTr("Members of %1").arg(members.roomName)
|
title: qsTr("Members of %1").arg(members.roomName)
|
||||||
height: 650
|
height: 650
|
||||||
|
@ -83,9 +84,14 @@ ApplicationWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: RowLayout {
|
delegate: RowLayout {
|
||||||
|
id: del
|
||||||
|
|
||||||
|
width: ListView.view.width
|
||||||
spacing: Nheko.paddingMedium
|
spacing: Nheko.paddingMedium
|
||||||
|
|
||||||
Avatar {
|
Avatar {
|
||||||
|
id: avatar
|
||||||
|
|
||||||
width: Nheko.avatarSize
|
width: Nheko.avatarSize
|
||||||
height: Nheko.avatarSize
|
height: Nheko.avatarSize
|
||||||
userid: model.mxid
|
userid: model.mxid
|
||||||
|
@ -97,16 +103,18 @@ ApplicationWindow {
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
spacing: Nheko.paddingSmall
|
spacing: Nheko.paddingSmall
|
||||||
|
|
||||||
Label {
|
ElidedLabel {
|
||||||
text: model.displayName
|
fullText: model.displayName
|
||||||
color: TimelineManager.userColor(model ? model.mxid : "", Nheko.colors.window)
|
color: TimelineManager.userColor(model ? model.mxid : "", Nheko.colors.window)
|
||||||
font.pointSize: fontMetrics.font.pointSize
|
font.pixelSize: fontMetrics.font.pixelSize
|
||||||
|
elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
ElidedLabel {
|
||||||
text: model.mxid
|
fullText: model.mxid
|
||||||
color: Nheko.colors.buttonText
|
color: Nheko.colors.buttonText
|
||||||
font.pointSize: fontMetrics.font.pointSize * 0.9
|
font.pixelSize: Math.ceil(fontMetrics.font.pixelSize * 0.9)
|
||||||
|
elideWidth: del.width - Nheko.paddingMedium * 2 - avatar.width - encryptInd.width
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
@ -116,6 +124,28 @@ ApplicationWindow {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EncryptionIndicator {
|
||||||
|
id: encryptInd
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
visible: room.isEncrypted
|
||||||
|
encrypted: room.isEncrypted
|
||||||
|
trust: encrypted ? model.trustlevel : Crypto.Unverified
|
||||||
|
ToolTip.text: {
|
||||||
|
if (!encrypted)
|
||||||
|
return qsTr("This room is not encrypted!");
|
||||||
|
|
||||||
|
switch (trust) {
|
||||||
|
case Crypto.Verified:
|
||||||
|
return qsTr("This user is verified.");
|
||||||
|
case Crypto.TOFU:
|
||||||
|
return qsTr("This user isn't verified, but is still using the same master key from the first time you met.");
|
||||||
|
default:
|
||||||
|
return qsTr("This user has unverified devices!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
footer: Item {
|
footer: Item {
|
||||||
|
|
|
@ -15,8 +15,8 @@ ApplicationWindow {
|
||||||
|
|
||||||
property var roomSettings
|
property var roomSettings
|
||||||
|
|
||||||
minimumWidth: 420
|
minimumWidth: 450
|
||||||
minimumHeight: 650
|
minimumHeight: 680
|
||||||
palette: Nheko.colors
|
palette: Nheko.colors
|
||||||
color: Nheko.colors.window
|
color: Nheko.colors.window
|
||||||
modality: Qt.NonModal
|
modality: Qt.NonModal
|
||||||
|
|
|
@ -8,7 +8,6 @@ import "./dialogs"
|
||||||
import "./emoji"
|
import "./emoji"
|
||||||
import "./voip"
|
import "./voip"
|
||||||
import Qt.labs.platform 1.1 as Platform
|
import Qt.labs.platform 1.1 as Platform
|
||||||
import QtGraphicalEffects 1.0
|
|
||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.15
|
import QtQuick.Controls 2.15
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
|
@ -153,10 +152,10 @@ Page {
|
||||||
packSet.show();
|
packSet.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onOpenRoomMembersDialog(members) {
|
function onOpenRoomMembersDialog(members, room) {
|
||||||
var membersDialog = roomMembersComponent.createObject(timelineRoot, {
|
var membersDialog = roomMembersComponent.createObject(timelineRoot, {
|
||||||
"members": members,
|
"members": members,
|
||||||
"roomName": Rooms.currentRoom.roomName
|
"room": room
|
||||||
});
|
});
|
||||||
membersDialog.show();
|
membersDialog.show();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import "./emoji"
|
||||||
import "./ui"
|
import "./ui"
|
||||||
import "./voip"
|
import "./voip"
|
||||||
import Qt.labs.platform 1.1 as Platform
|
import Qt.labs.platform 1.1 as Platform
|
||||||
import QtGraphicalEffects 1.0
|
|
||||||
import QtQuick 2.9
|
import QtQuick 2.9
|
||||||
import QtQuick.Controls 2.5
|
import QtQuick.Controls 2.5
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
|
@ -85,9 +84,14 @@ Item {
|
||||||
target: timelineView
|
target: timelineView
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageView {
|
Loader {
|
||||||
|
active: room || roomPreview
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
implicitHeight: msgView.height - typingIndicator.height
|
|
||||||
|
sourceComponent: MessageView {
|
||||||
|
implicitHeight: msgView.height - typingIndicator.height
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
|
|
|
@ -15,6 +15,8 @@ Rectangle {
|
||||||
property string roomName: room ? room.roomName : qsTr("No room selected")
|
property string roomName: room ? room.roomName : qsTr("No room selected")
|
||||||
property string avatarUrl: room ? room.roomAvatarUrl : ""
|
property string avatarUrl: room ? room.roomAvatarUrl : ""
|
||||||
property string roomTopic: room ? room.roomTopic : ""
|
property string roomTopic: room ? room.roomTopic : ""
|
||||||
|
property bool isEncrypted: room ? room.isEncrypted : false
|
||||||
|
property int trustlevel: room ? room.trustlevel : Crypto.Unverified
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
implicitHeight: topLayout.height + Nheko.paddingMedium * 2
|
implicitHeight: topLayout.height + Nheko.paddingMedium * 2
|
||||||
|
@ -92,11 +94,33 @@ Rectangle {
|
||||||
text: roomTopic
|
text: roomTopic
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EncryptionIndicator {
|
||||||
|
Layout.column: 3
|
||||||
|
Layout.row: 0
|
||||||
|
Layout.rowSpan: 2
|
||||||
|
visible: isEncrypted
|
||||||
|
encrypted: isEncrypted
|
||||||
|
trust: trustlevel
|
||||||
|
ToolTip.text: {
|
||||||
|
if (!encrypted)
|
||||||
|
return qsTr("This room is not encrypted!");
|
||||||
|
|
||||||
|
switch (trust) {
|
||||||
|
case Crypto.Verified:
|
||||||
|
return qsTr("This room contains only verified devices.");
|
||||||
|
case Crypto.TOFU:
|
||||||
|
return qsTr("This rooms contain verified devices and devices which have never changed their master key.");
|
||||||
|
default:
|
||||||
|
return qsTr("This room contains unverified devices!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
id: roomOptionsButton
|
id: roomOptionsButton
|
||||||
|
|
||||||
visible: !!room
|
visible: !!room
|
||||||
Layout.column: 3
|
Layout.column: 4
|
||||||
Layout.row: 0
|
Layout.row: 0
|
||||||
Layout.rowSpan: 2
|
Layout.rowSpan: 2
|
||||||
Layout.alignment: Qt.AlignVCenter
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
@ -116,7 +140,7 @@ Rectangle {
|
||||||
|
|
||||||
Platform.MenuItem {
|
Platform.MenuItem {
|
||||||
text: qsTr("Members")
|
text: qsTr("Members")
|
||||||
onTriggered: TimelineManager.openRoomMembers(room.roomId)
|
onTriggered: TimelineManager.openRoomMembers(room)
|
||||||
}
|
}
|
||||||
|
|
||||||
Platform.MenuItem {
|
Platform.MenuItem {
|
||||||
|
|
|
@ -171,7 +171,7 @@ ApplicationWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixText {
|
MatrixText {
|
||||||
text: qsTr("Attrbution")
|
text: qsTr("Attribution")
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixTextField {
|
MatrixTextField {
|
||||||
|
|
392
src/Cache.cpp
392
src/Cache.cpp
|
@ -114,7 +114,13 @@ ro_txn(lmdb::env &env)
|
||||||
txn = lmdb::txn::begin(env, nullptr, MDB_RDONLY);
|
txn = lmdb::txn::begin(env, nullptr, MDB_RDONLY);
|
||||||
reuse_counter = 0;
|
reuse_counter = 0;
|
||||||
} else if (reuse_counter > 0) {
|
} else if (reuse_counter > 0) {
|
||||||
txn.renew();
|
try {
|
||||||
|
txn.renew();
|
||||||
|
} catch (...) {
|
||||||
|
txn.abort();
|
||||||
|
txn = lmdb::txn::begin(env, nullptr, MDB_RDONLY);
|
||||||
|
reuse_counter = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
reuse_counter++;
|
reuse_counter++;
|
||||||
|
|
||||||
|
@ -289,7 +295,9 @@ Cache::setup()
|
||||||
megolmSessionDataDb_ = lmdb::dbi::open(txn, MEGOLM_SESSIONS_DATA_DB, MDB_CREATE);
|
megolmSessionDataDb_ = lmdb::dbi::open(txn, MEGOLM_SESSIONS_DATA_DB, MDB_CREATE);
|
||||||
|
|
||||||
// What rooms are encrypted
|
// What rooms are encrypted
|
||||||
encryptedRooms_ = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE);
|
encryptedRooms_ = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE);
|
||||||
|
[[maybe_unused]] auto verificationDb = getVerificationDb(txn);
|
||||||
|
[[maybe_unused]] auto userKeysDb = getUserKeysDb(txn);
|
||||||
|
|
||||||
txn.commit();
|
txn.commit();
|
||||||
|
|
||||||
|
@ -720,20 +728,35 @@ Cache::storeSecret(const std::string name, const std::string secret)
|
||||||
{
|
{
|
||||||
auto settings = UserSettings::instance();
|
auto settings = UserSettings::instance();
|
||||||
auto job = new QKeychain::WritePasswordJob(QCoreApplication::applicationName());
|
auto job = new QKeychain::WritePasswordJob(QCoreApplication::applicationName());
|
||||||
|
job->setAutoDelete(true);
|
||||||
job->setInsecureFallback(true);
|
job->setInsecureFallback(true);
|
||||||
job->setKey("matrix." +
|
job->setSettings(UserSettings::instance()->qsettings());
|
||||||
QString(QCryptographicHash::hash(settings->profile().toUtf8(),
|
|
||||||
QCryptographicHash::Sha256)) +
|
job->setKey(
|
||||||
"." + name.c_str());
|
"matrix." +
|
||||||
|
QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256)
|
||||||
|
.toBase64()) +
|
||||||
|
"." + QString::fromStdString(name));
|
||||||
|
|
||||||
job->setTextData(QString::fromStdString(secret));
|
job->setTextData(QString::fromStdString(secret));
|
||||||
QObject::connect(job, &QKeychain::Job::finished, job, [name, this](QKeychain::Job *job) {
|
|
||||||
if (job->error()) {
|
QObject::connect(
|
||||||
nhlog::db()->warn(
|
job,
|
||||||
"Storing secret '{}' failed: {}", name, job->errorString().toStdString());
|
&QKeychain::WritePasswordJob::finished,
|
||||||
} else {
|
this,
|
||||||
emit secretChanged(name);
|
[name, this](QKeychain::Job *job) {
|
||||||
}
|
if (job->error()) {
|
||||||
});
|
nhlog::db()->warn("Storing secret '{}' failed: {}",
|
||||||
|
name,
|
||||||
|
job->errorString().toStdString());
|
||||||
|
} else {
|
||||||
|
// if we emit the signal directly, qtkeychain breaks and won't execute new
|
||||||
|
// jobs. You can't start a job from the finish signal of a job.
|
||||||
|
QTimer::singleShot(100, [this, name] { emit secretChanged(name); });
|
||||||
|
nhlog::db()->info("Storing secret '{}' successful", name);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Qt::ConnectionType::DirectConnection);
|
||||||
job->start();
|
job->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -744,10 +767,14 @@ Cache::deleteSecret(const std::string name)
|
||||||
QKeychain::DeletePasswordJob job(QCoreApplication::applicationName());
|
QKeychain::DeletePasswordJob job(QCoreApplication::applicationName());
|
||||||
job.setAutoDelete(false);
|
job.setAutoDelete(false);
|
||||||
job.setInsecureFallback(true);
|
job.setInsecureFallback(true);
|
||||||
job.setKey("matrix." +
|
job.setSettings(UserSettings::instance()->qsettings());
|
||||||
QString(QCryptographicHash::hash(settings->profile().toUtf8(),
|
|
||||||
QCryptographicHash::Sha256)) +
|
job.setKey(
|
||||||
"." + name.c_str());
|
"matrix." +
|
||||||
|
QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256)
|
||||||
|
.toBase64()) +
|
||||||
|
"." + QString::fromStdString(name));
|
||||||
|
|
||||||
// FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean
|
// FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean
|
||||||
// time!
|
// time!
|
||||||
QEventLoop loop;
|
QEventLoop loop;
|
||||||
|
@ -765,10 +792,14 @@ Cache::secret(const std::string name)
|
||||||
QKeychain::ReadPasswordJob job(QCoreApplication::applicationName());
|
QKeychain::ReadPasswordJob job(QCoreApplication::applicationName());
|
||||||
job.setAutoDelete(false);
|
job.setAutoDelete(false);
|
||||||
job.setInsecureFallback(true);
|
job.setInsecureFallback(true);
|
||||||
job.setKey("matrix." +
|
job.setSettings(UserSettings::instance()->qsettings());
|
||||||
QString(QCryptographicHash::hash(settings->profile().toUtf8(),
|
|
||||||
QCryptographicHash::Sha256)) +
|
job.setKey(
|
||||||
"." + name.c_str());
|
"matrix." +
|
||||||
|
QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256)
|
||||||
|
.toBase64()) +
|
||||||
|
"." + QString::fromStdString(name));
|
||||||
|
|
||||||
// FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean
|
// FIXME(Nico): Nested event loops are dangerous. Some other slots may resume in the mean
|
||||||
// time!
|
// time!
|
||||||
QEventLoop loop;
|
QEventLoop loop;
|
||||||
|
@ -838,6 +869,9 @@ Cache::setNextBatchToken(lmdb::txn &txn, const QString &token)
|
||||||
bool
|
bool
|
||||||
Cache::isInitialized()
|
Cache::isInitialized()
|
||||||
{
|
{
|
||||||
|
if (!env_.handle())
|
||||||
|
return false;
|
||||||
|
|
||||||
auto txn = ro_txn(env_);
|
auto txn = ro_txn(env_);
|
||||||
std::string_view token;
|
std::string_view token;
|
||||||
|
|
||||||
|
@ -1563,26 +1597,32 @@ Cache::roomsWithStateUpdates(const mtx::responses::Sync &res)
|
||||||
RoomInfo
|
RoomInfo
|
||||||
Cache::singleRoomInfo(const std::string &room_id)
|
Cache::singleRoomInfo(const std::string &room_id)
|
||||||
{
|
{
|
||||||
auto txn = ro_txn(env_);
|
auto txn = ro_txn(env_);
|
||||||
auto statesdb = getStatesDb(txn, room_id);
|
|
||||||
|
|
||||||
std::string_view data;
|
try {
|
||||||
|
auto statesdb = getStatesDb(txn, room_id);
|
||||||
|
|
||||||
// Check if the room is joined.
|
std::string_view data;
|
||||||
if (roomsDb_.get(txn, room_id, data)) {
|
|
||||||
try {
|
|
||||||
RoomInfo tmp = json::parse(data);
|
|
||||||
tmp.member_count = getMembersDb(txn, room_id).size(txn);
|
|
||||||
tmp.join_rule = getRoomJoinRule(txn, statesdb);
|
|
||||||
tmp.guest_access = getRoomGuestAccess(txn, statesdb);
|
|
||||||
|
|
||||||
return tmp;
|
// Check if the room is joined.
|
||||||
} catch (const json::exception &e) {
|
if (roomsDb_.get(txn, room_id, data)) {
|
||||||
nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}",
|
try {
|
||||||
room_id,
|
RoomInfo tmp = json::parse(data);
|
||||||
std::string(data.data(), data.size()),
|
tmp.member_count = getMembersDb(txn, room_id).size(txn);
|
||||||
e.what());
|
tmp.join_rule = getRoomJoinRule(txn, statesdb);
|
||||||
|
tmp.guest_access = getRoomGuestAccess(txn, statesdb);
|
||||||
|
|
||||||
|
return tmp;
|
||||||
|
} catch (const json::exception &e) {
|
||||||
|
nhlog::db()->warn("failed to parse room info: room_id ({}), {}: {}",
|
||||||
|
room_id,
|
||||||
|
std::string(data.data(), data.size()),
|
||||||
|
e.what());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (const lmdb::error &e) {
|
||||||
|
nhlog::db()->warn(
|
||||||
|
"failed to read room info from db: room_id ({}), {}", room_id, e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
return RoomInfo();
|
return RoomInfo();
|
||||||
|
@ -3541,6 +3581,44 @@ Cache::roomMembers(const std::string &room_id)
|
||||||
return members;
|
return members;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crypto::Trust
|
||||||
|
Cache::roomVerificationStatus(const std::string &room_id)
|
||||||
|
{
|
||||||
|
crypto::Trust trust = crypto::Verified;
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto txn = lmdb::txn::begin(env_);
|
||||||
|
|
||||||
|
auto db = getMembersDb(txn, room_id);
|
||||||
|
auto keysDb = getUserKeysDb(txn);
|
||||||
|
std::vector<std::string> keysToRequest;
|
||||||
|
|
||||||
|
std::string_view user_id, unused;
|
||||||
|
auto cursor = lmdb::cursor::open(txn, db);
|
||||||
|
while (cursor.get(user_id, unused, MDB_NEXT)) {
|
||||||
|
auto verif = verificationStatus_(std::string(user_id), txn);
|
||||||
|
if (verif.unverified_device_count) {
|
||||||
|
trust = crypto::Unverified;
|
||||||
|
if (verif.verified_devices.empty() && verif.no_keys) {
|
||||||
|
// we probably don't have the keys yet, so query them
|
||||||
|
keysToRequest.push_back(std::string(user_id));
|
||||||
|
}
|
||||||
|
} else if (verif.user_verified == crypto::TOFU && trust == crypto::Verified)
|
||||||
|
trust = crypto::TOFU;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keysToRequest.empty())
|
||||||
|
markUserKeysOutOfDate(txn, keysDb, keysToRequest, "");
|
||||||
|
|
||||||
|
} catch (std::exception &e) {
|
||||||
|
nhlog::db()->error(
|
||||||
|
"Failed to calculate verification status for {}: {}", room_id, e.what());
|
||||||
|
trust = crypto::Unverified;
|
||||||
|
}
|
||||||
|
|
||||||
|
return trust;
|
||||||
|
}
|
||||||
|
|
||||||
std::map<std::string, std::optional<UserKeyCache>>
|
std::map<std::string, std::optional<UserKeyCache>>
|
||||||
Cache::getMembersWithKeys(const std::string &room_id, bool verified_only)
|
Cache::getMembersWithKeys(const std::string &room_id, bool verified_only)
|
||||||
{
|
{
|
||||||
|
@ -3722,11 +3800,17 @@ from_json(const json &j, UserKeyCache &info)
|
||||||
|
|
||||||
std::optional<UserKeyCache>
|
std::optional<UserKeyCache>
|
||||||
Cache::userKeys(const std::string &user_id)
|
Cache::userKeys(const std::string &user_id)
|
||||||
|
{
|
||||||
|
auto txn = ro_txn(env_);
|
||||||
|
return userKeys_(user_id, txn);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<UserKeyCache>
|
||||||
|
Cache::userKeys_(const std::string &user_id, lmdb::txn &txn)
|
||||||
{
|
{
|
||||||
std::string_view keys;
|
std::string_view keys;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto txn = ro_txn(env_);
|
|
||||||
auto db = getUserKeysDb(txn);
|
auto db = getUserKeysDb(txn);
|
||||||
auto res = db.get(txn, user_id, keys);
|
auto res = db.get(txn, user_id, keys);
|
||||||
|
|
||||||
|
@ -3735,7 +3819,8 @@ Cache::userKeys(const std::string &user_id)
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
} catch (std::exception &) {
|
} catch (std::exception &e) {
|
||||||
|
nhlog::db()->error("Failed to retrieve user keys for {}: {}", user_id, e.what());
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3770,8 +3855,14 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query
|
||||||
auto last_changed = updateToWrite.last_changed;
|
auto last_changed = updateToWrite.last_changed;
|
||||||
// skip if we are tracking this and expect it to be up to date with the last
|
// skip if we are tracking this and expect it to be up to date with the last
|
||||||
// sync token
|
// sync token
|
||||||
if (!last_changed.empty() && last_changed != sync_token)
|
if (!last_changed.empty() && last_changed != sync_token) {
|
||||||
|
nhlog::db()->debug("Not storing update for user {}, because "
|
||||||
|
"last_changed {}, but we fetched update for {}",
|
||||||
|
user,
|
||||||
|
last_changed,
|
||||||
|
sync_token);
|
||||||
continue;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!updateToWrite.master_keys.keys.empty() &&
|
if (!updateToWrite.master_keys.keys.empty() &&
|
||||||
update.master_keys.keys != updateToWrite.master_keys.keys) {
|
update.master_keys.keys != updateToWrite.master_keys.keys) {
|
||||||
|
@ -3819,8 +3910,43 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!keyReused && !oldDeviceKeys.count(device_id))
|
if (!keyReused && !oldDeviceKeys.count(device_id)) {
|
||||||
|
// ensure the key has a valid signature from itself
|
||||||
|
std::string device_signing_key =
|
||||||
|
"ed25519:" + device_keys.device_id;
|
||||||
|
if (device_id != device_keys.device_id) {
|
||||||
|
nhlog::crypto()->warn(
|
||||||
|
"device {}:{} has a different device id "
|
||||||
|
"in the body: {}",
|
||||||
|
user,
|
||||||
|
device_id,
|
||||||
|
device_keys.device_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!device_keys.signatures.count(user) ||
|
||||||
|
!device_keys.signatures.at(user).count(
|
||||||
|
device_signing_key)) {
|
||||||
|
nhlog::crypto()->warn(
|
||||||
|
"device {}:{} has no signature",
|
||||||
|
user,
|
||||||
|
device_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mtx::crypto::ed25519_verify_signature(
|
||||||
|
device_keys.keys.at(device_signing_key),
|
||||||
|
json(device_keys),
|
||||||
|
device_keys.signatures.at(user).at(
|
||||||
|
device_signing_key))) {
|
||||||
|
nhlog::crypto()->warn(
|
||||||
|
"device {}:{} has an invalid signature",
|
||||||
|
user,
|
||||||
|
device_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
updateToWrite.device_keys[device_id] = device_keys;
|
updateToWrite.device_keys[device_id] = device_keys;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &[key_id, key] : device_keys.keys) {
|
for (const auto &[key_id, key] : device_keys.keys) {
|
||||||
|
@ -3830,6 +3956,7 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query
|
||||||
updateToWrite.seen_device_ids.insert(device_id);
|
updateToWrite.seen_device_ids.insert(device_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
updateToWrite.updated_at = sync_token;
|
||||||
db.put(txn, user, json(updateToWrite).dump());
|
db.put(txn, user, json(updateToWrite).dump());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3882,14 +4009,15 @@ Cache::markUserKeysOutOfDate(lmdb::txn &txn,
|
||||||
nhlog::db()->debug("Marking user keys out of date: {}", user);
|
nhlog::db()->debug("Marking user keys out of date: {}", user);
|
||||||
|
|
||||||
std::string_view oldKeys;
|
std::string_view oldKeys;
|
||||||
|
|
||||||
|
UserKeyCache cacheEntry;
|
||||||
auto res = db.get(txn, user, oldKeys);
|
auto res = db.get(txn, user, oldKeys);
|
||||||
|
if (res) {
|
||||||
if (!res)
|
cacheEntry = json::parse(std::string_view(oldKeys.data(), oldKeys.size()))
|
||||||
continue;
|
.get<UserKeyCache>();
|
||||||
|
}
|
||||||
auto cacheEntry =
|
|
||||||
json::parse(std::string_view(oldKeys.data(), oldKeys.size())).get<UserKeyCache>();
|
|
||||||
cacheEntry.last_changed = sync_token;
|
cacheEntry.last_changed = sync_token;
|
||||||
|
|
||||||
db.put(txn, user, json(cacheEntry).dump());
|
db.put(txn, user, json(cacheEntry).dump());
|
||||||
|
|
||||||
query.device_keys[user] = {};
|
query.device_keys[user] = {};
|
||||||
|
@ -3915,35 +4043,46 @@ void
|
||||||
Cache::query_keys(const std::string &user_id,
|
Cache::query_keys(const std::string &user_id,
|
||||||
std::function<void(const UserKeyCache &, mtx::http::RequestErr)> cb)
|
std::function<void(const UserKeyCache &, mtx::http::RequestErr)> cb)
|
||||||
{
|
{
|
||||||
auto cache_ = cache::userKeys(user_id);
|
|
||||||
|
|
||||||
if (cache_.has_value()) {
|
|
||||||
if (!cache_->updated_at.empty() && cache_->updated_at == cache_->last_changed) {
|
|
||||||
cb(cache_.value(), {});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mtx::requests::QueryKeys req;
|
mtx::requests::QueryKeys req;
|
||||||
req.device_keys[user_id] = {};
|
|
||||||
|
|
||||||
std::string last_changed;
|
std::string last_changed;
|
||||||
if (cache_)
|
{
|
||||||
last_changed = cache_->last_changed;
|
auto txn = ro_txn(env_);
|
||||||
req.token = last_changed;
|
auto cache_ = userKeys_(user_id, txn);
|
||||||
|
|
||||||
|
if (cache_.has_value()) {
|
||||||
|
if (cache_->updated_at == cache_->last_changed) {
|
||||||
|
cb(cache_.value(), {});
|
||||||
|
return;
|
||||||
|
} else
|
||||||
|
nhlog::db()->info("Keys outdated for {}: {} vs {}",
|
||||||
|
user_id,
|
||||||
|
cache_->updated_at,
|
||||||
|
cache_->last_changed);
|
||||||
|
} else
|
||||||
|
nhlog::db()->info("No keys found for {}", user_id);
|
||||||
|
|
||||||
|
req.device_keys[user_id] = {};
|
||||||
|
|
||||||
|
if (cache_)
|
||||||
|
last_changed = cache_->last_changed;
|
||||||
|
req.token = last_changed;
|
||||||
|
}
|
||||||
|
|
||||||
// use context object so that we can disconnect again
|
// use context object so that we can disconnect again
|
||||||
QObject *context{new QObject(this)};
|
QObject *context{new QObject(this)};
|
||||||
QObject::connect(this,
|
QObject::connect(
|
||||||
&Cache::verificationStatusChanged,
|
this,
|
||||||
context,
|
&Cache::verificationStatusChanged,
|
||||||
[cb, user_id, context_ = context](std::string updated_user) mutable {
|
context,
|
||||||
if (user_id == updated_user) {
|
[cb, user_id, context_ = context, this](std::string updated_user) mutable {
|
||||||
context_->deleteLater();
|
if (user_id == updated_user) {
|
||||||
auto keys = cache::userKeys(user_id);
|
context_->deleteLater();
|
||||||
cb(keys.value_or(UserKeyCache{}), {});
|
auto txn = ro_txn(env_);
|
||||||
}
|
auto keys = this->userKeys_(user_id, txn);
|
||||||
});
|
cb(keys.value_or(UserKeyCache{}), {});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
|
||||||
http::client()->query_keys(
|
http::client()->query_keys(
|
||||||
req,
|
req,
|
||||||
|
@ -3971,17 +4110,16 @@ to_json(json &j, const VerificationCache &info)
|
||||||
void
|
void
|
||||||
from_json(const json &j, VerificationCache &info)
|
from_json(const json &j, VerificationCache &info)
|
||||||
{
|
{
|
||||||
info.device_verified = j.at("device_verified").get<std::vector<std::string>>();
|
info.device_verified = j.at("device_verified").get<std::set<std::string>>();
|
||||||
info.device_blocked = j.at("device_blocked").get<std::vector<std::string>>();
|
info.device_blocked = j.at("device_blocked").get<std::set<std::string>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<VerificationCache>
|
std::optional<VerificationCache>
|
||||||
Cache::verificationCache(const std::string &user_id)
|
Cache::verificationCache(const std::string &user_id, lmdb::txn &txn)
|
||||||
{
|
{
|
||||||
std::string_view verifiedVal;
|
std::string_view verifiedVal;
|
||||||
|
|
||||||
auto txn = lmdb::txn::begin(env_);
|
auto db = getVerificationDb(txn);
|
||||||
auto db = getVerificationDb(txn);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
VerificationCache verified_state;
|
VerificationCache verified_state;
|
||||||
|
@ -4000,26 +4138,28 @@ Cache::verificationCache(const std::string &user_id)
|
||||||
void
|
void
|
||||||
Cache::markDeviceVerified(const std::string &user_id, const std::string &key)
|
Cache::markDeviceVerified(const std::string &user_id, const std::string &key)
|
||||||
{
|
{
|
||||||
std::string_view val;
|
{
|
||||||
|
std::string_view val;
|
||||||
|
|
||||||
auto txn = lmdb::txn::begin(env_);
|
auto txn = lmdb::txn::begin(env_);
|
||||||
auto db = getVerificationDb(txn);
|
auto db = getVerificationDb(txn);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
VerificationCache verified_state;
|
VerificationCache verified_state;
|
||||||
auto res = db.get(txn, user_id, val);
|
auto res = db.get(txn, user_id, val);
|
||||||
if (res) {
|
if (res) {
|
||||||
verified_state = json::parse(val);
|
verified_state = json::parse(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &device : verified_state.device_verified)
|
||||||
|
if (device == key)
|
||||||
|
return;
|
||||||
|
|
||||||
|
verified_state.device_verified.insert(key);
|
||||||
|
db.put(txn, user_id, json(verified_state).dump());
|
||||||
|
txn.commit();
|
||||||
|
} catch (std::exception &) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &device : verified_state.device_verified)
|
|
||||||
if (device == key)
|
|
||||||
return;
|
|
||||||
|
|
||||||
verified_state.device_verified.push_back(key);
|
|
||||||
db.put(txn, user_id, json(verified_state).dump());
|
|
||||||
txn.commit();
|
|
||||||
} catch (std::exception &) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto local_user = utils::localUser().toStdString();
|
const auto local_user = utils::localUser().toStdString();
|
||||||
|
@ -4057,11 +4197,7 @@ Cache::markDeviceUnverified(const std::string &user_id, const std::string &key)
|
||||||
verified_state = json::parse(val);
|
verified_state = json::parse(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
verified_state.device_verified.erase(
|
verified_state.device_verified.erase(key);
|
||||||
std::remove(verified_state.device_verified.begin(),
|
|
||||||
verified_state.device_verified.end(),
|
|
||||||
key),
|
|
||||||
verified_state.device_verified.end());
|
|
||||||
|
|
||||||
db.put(txn, user_id, json(verified_state).dump());
|
db.put(txn, user_id, json(verified_state).dump());
|
||||||
txn.commit();
|
txn.commit();
|
||||||
|
@ -4090,6 +4226,13 @@ Cache::markDeviceUnverified(const std::string &user_id, const std::string &key)
|
||||||
|
|
||||||
VerificationStatus
|
VerificationStatus
|
||||||
Cache::verificationStatus(const std::string &user_id)
|
Cache::verificationStatus(const std::string &user_id)
|
||||||
|
{
|
||||||
|
auto txn = ro_txn(env_);
|
||||||
|
return verificationStatus_(user_id, txn);
|
||||||
|
}
|
||||||
|
|
||||||
|
VerificationStatus
|
||||||
|
Cache::verificationStatus_(const std::string &user_id, lmdb::txn &txn)
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(verification_storage.verification_storage_mtx);
|
std::unique_lock<std::mutex> lock(verification_storage.verification_storage_mtx);
|
||||||
if (verification_storage.status.count(user_id))
|
if (verification_storage.status.count(user_id))
|
||||||
|
@ -4097,7 +4240,12 @@ Cache::verificationStatus(const std::string &user_id)
|
||||||
|
|
||||||
VerificationStatus status;
|
VerificationStatus status;
|
||||||
|
|
||||||
if (auto verifCache = verificationCache(user_id)) {
|
// assume there is at least one unverified device until we have checked we have the device
|
||||||
|
// list for that user.
|
||||||
|
status.unverified_device_count = 1;
|
||||||
|
status.no_keys = true;
|
||||||
|
|
||||||
|
if (auto verifCache = verificationCache(user_id, txn)) {
|
||||||
status.verified_devices = verifCache->device_verified;
|
status.verified_devices = verifCache->device_verified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4105,12 +4253,10 @@ Cache::verificationStatus(const std::string &user_id)
|
||||||
|
|
||||||
crypto::Trust trustlevel = crypto::Trust::Unverified;
|
crypto::Trust trustlevel = crypto::Trust::Unverified;
|
||||||
if (user_id == local_user) {
|
if (user_id == local_user) {
|
||||||
status.verified_devices.push_back(http::client()->device_id());
|
status.verified_devices.insert(http::client()->device_id());
|
||||||
trustlevel = crypto::Trust::Verified;
|
trustlevel = crypto::Trust::Verified;
|
||||||
}
|
}
|
||||||
|
|
||||||
verification_storage.status[user_id] = status;
|
|
||||||
|
|
||||||
auto verifyAtLeastOneSig = [](const auto &toVerif,
|
auto verifyAtLeastOneSig = [](const auto &toVerif,
|
||||||
const std::map<std::string, std::string> &keys,
|
const std::map<std::string, std::string> &keys,
|
||||||
const std::string &keyOwner) {
|
const std::string &keyOwner) {
|
||||||
|
@ -4128,6 +4274,16 @@ Cache::verificationStatus(const std::string &user_id)
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto updateUnverifiedDevices = [&status](auto &theirDeviceKeys) {
|
||||||
|
int currentVerifiedDevices = 0;
|
||||||
|
for (auto device_id : status.verified_devices) {
|
||||||
|
if (theirDeviceKeys.count(device_id))
|
||||||
|
currentVerifiedDevices++;
|
||||||
|
}
|
||||||
|
status.unverified_device_count =
|
||||||
|
static_cast<int>(theirDeviceKeys.size()) - currentVerifiedDevices;
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// for local user verify this device_key -> our master_key -> our self_signing_key
|
// for local user verify this device_key -> our master_key -> our self_signing_key
|
||||||
// -> our device_keys
|
// -> our device_keys
|
||||||
|
@ -4137,17 +4293,27 @@ Cache::verificationStatus(const std::string &user_id)
|
||||||
//
|
//
|
||||||
// This means verifying the other user adds 2 extra steps,verifying our user_signing
|
// This means verifying the other user adds 2 extra steps,verifying our user_signing
|
||||||
// key and their master key
|
// key and their master key
|
||||||
auto ourKeys = userKeys(local_user);
|
auto ourKeys = userKeys_(local_user, txn);
|
||||||
auto theirKeys = userKeys(user_id);
|
auto theirKeys = userKeys_(user_id, txn);
|
||||||
if (!ourKeys || !theirKeys)
|
if (theirKeys)
|
||||||
|
status.no_keys = false;
|
||||||
|
|
||||||
|
if (!ourKeys || !theirKeys) {
|
||||||
|
verification_storage.status[user_id] = status;
|
||||||
return status;
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update verified devices count to count without cross-signing
|
||||||
|
updateUnverifiedDevices(theirKeys->device_keys);
|
||||||
|
|
||||||
if (!mtx::crypto::ed25519_verify_signature(
|
if (!mtx::crypto::ed25519_verify_signature(
|
||||||
olm::client()->identity_keys().ed25519,
|
olm::client()->identity_keys().ed25519,
|
||||||
json(ourKeys->master_keys),
|
json(ourKeys->master_keys),
|
||||||
ourKeys->master_keys.signatures.at(local_user)
|
ourKeys->master_keys.signatures.at(local_user)
|
||||||
.at("ed25519:" + http::client()->device_id())))
|
.at("ed25519:" + http::client()->device_id()))) {
|
||||||
|
verification_storage.status[user_id] = status;
|
||||||
return status;
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
auto master_keys = ourKeys->master_keys.keys;
|
auto master_keys = ourKeys->master_keys.keys;
|
||||||
|
|
||||||
|
@ -4162,14 +4328,17 @@ Cache::verificationStatus(const std::string &user_id)
|
||||||
trustlevel = crypto::Trust::Verified;
|
trustlevel = crypto::Trust::Verified;
|
||||||
else if (!theirKeys->master_key_changed)
|
else if (!theirKeys->master_key_changed)
|
||||||
trustlevel = crypto::Trust::TOFU;
|
trustlevel = crypto::Trust::TOFU;
|
||||||
else
|
else {
|
||||||
|
verification_storage.status[user_id] = status;
|
||||||
return status;
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
master_keys = theirKeys->master_keys.keys;
|
master_keys = theirKeys->master_keys.keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
status.user_verified = trustlevel;
|
status.user_verified = trustlevel;
|
||||||
|
|
||||||
|
verification_storage.status[user_id] = status;
|
||||||
if (!verifyAtLeastOneSig(theirKeys->self_signing_keys, master_keys, user_id))
|
if (!verifyAtLeastOneSig(theirKeys->self_signing_keys, master_keys, user_id))
|
||||||
return status;
|
return status;
|
||||||
|
|
||||||
|
@ -4180,16 +4349,19 @@ Cache::verificationStatus(const std::string &user_id)
|
||||||
device_key.keys.at("curve25519:" + device_key.device_id);
|
device_key.keys.at("curve25519:" + device_key.device_id);
|
||||||
if (verifyAtLeastOneSig(
|
if (verifyAtLeastOneSig(
|
||||||
device_key, theirKeys->self_signing_keys.keys, user_id)) {
|
device_key, theirKeys->self_signing_keys.keys, user_id)) {
|
||||||
status.verified_devices.push_back(device_key.device_id);
|
status.verified_devices.insert(device_key.device_id);
|
||||||
status.verified_device_keys[identkey] = trustlevel;
|
status.verified_device_keys[identkey] = trustlevel;
|
||||||
}
|
}
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateUnverifiedDevices(theirKeys->device_keys);
|
||||||
verification_storage.status[user_id] = status;
|
verification_storage.status[user_id] = status;
|
||||||
return status;
|
return status;
|
||||||
} catch (std::exception &) {
|
} catch (std::exception &e) {
|
||||||
|
nhlog::db()->error(
|
||||||
|
"Failed to calculate verification status of {}: {}", user_id, e.what());
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,9 +112,13 @@ struct VerificationStatus
|
||||||
//! True, if the users master key is verified
|
//! True, if the users master key is verified
|
||||||
crypto::Trust user_verified = crypto::Trust::Unverified;
|
crypto::Trust user_verified = crypto::Trust::Unverified;
|
||||||
//! List of all devices marked as verified
|
//! List of all devices marked as verified
|
||||||
std::vector<std::string> verified_devices;
|
std::set<std::string> verified_devices;
|
||||||
//! Map from sender key/curve25519 to trust status
|
//! Map from sender key/curve25519 to trust status
|
||||||
std::map<std::string, crypto::Trust> verified_device_keys;
|
std::map<std::string, crypto::Trust> verified_device_keys;
|
||||||
|
//! Count of unverified devices
|
||||||
|
int unverified_device_count = 0;
|
||||||
|
// if the keys are not in cache
|
||||||
|
bool no_keys = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
//! In memory cache of verification status
|
//! In memory cache of verification status
|
||||||
|
@ -154,9 +158,9 @@ from_json(const nlohmann::json &j, UserKeyCache &info);
|
||||||
struct VerificationCache
|
struct VerificationCache
|
||||||
{
|
{
|
||||||
//! list of verified device_ids with device-verification
|
//! list of verified device_ids with device-verification
|
||||||
std::vector<std::string> device_verified;
|
std::set<std::string> device_verified;
|
||||||
//! list of devices the user blocks
|
//! list of devices the user blocks
|
||||||
std::vector<std::string> device_blocked;
|
std::set<std::string> device_blocked;
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -46,7 +46,6 @@ public:
|
||||||
std::string statusMessage(const std::string &user_id);
|
std::string statusMessage(const std::string &user_id);
|
||||||
|
|
||||||
// user cache stores user keys
|
// user cache stores user keys
|
||||||
std::optional<UserKeyCache> userKeys(const std::string &user_id);
|
|
||||||
std::map<std::string, std::optional<UserKeyCache>> getMembersWithKeys(
|
std::map<std::string, std::optional<UserKeyCache>> getMembersWithKeys(
|
||||||
const std::string &room_id,
|
const std::string &room_id,
|
||||||
bool verified_only);
|
bool verified_only);
|
||||||
|
@ -63,9 +62,11 @@ public:
|
||||||
std::function<void(const UserKeyCache &, mtx::http::RequestErr)> cb);
|
std::function<void(const UserKeyCache &, mtx::http::RequestErr)> cb);
|
||||||
|
|
||||||
// device & user verification cache
|
// device & user verification cache
|
||||||
|
std::optional<UserKeyCache> userKeys(const std::string &user_id);
|
||||||
VerificationStatus verificationStatus(const std::string &user_id);
|
VerificationStatus verificationStatus(const std::string &user_id);
|
||||||
void markDeviceVerified(const std::string &user_id, const std::string &device);
|
void markDeviceVerified(const std::string &user_id, const std::string &device);
|
||||||
void markDeviceUnverified(const std::string &user_id, const std::string &device);
|
void markDeviceUnverified(const std::string &user_id, const std::string &device);
|
||||||
|
crypto::Trust roomVerificationStatus(const std::string &room_id);
|
||||||
|
|
||||||
std::vector<std::string> joinedRooms();
|
std::vector<std::string> joinedRooms();
|
||||||
|
|
||||||
|
@ -414,24 +415,25 @@ private:
|
||||||
if constexpr (isStateEvent_<decltype(e)>) {
|
if constexpr (isStateEvent_<decltype(e)>) {
|
||||||
eventsDb.put(txn, e.event_id, json(e).dump());
|
eventsDb.put(txn, e.event_id, json(e).dump());
|
||||||
|
|
||||||
if (std::is_same_v<
|
if (e.type != EventType::Unsupported) {
|
||||||
std::remove_cv_t<std::remove_reference_t<decltype(e)>>,
|
if (std::is_same_v<
|
||||||
StateEvent<mtx::events::msg::Redacted>>) {
|
std::remove_cv_t<
|
||||||
if (e.type == EventType::RoomMember)
|
std::remove_reference_t<decltype(e)>>,
|
||||||
membersdb.del(txn, e.state_key, "");
|
StateEvent<mtx::events::msg::Redacted>>) {
|
||||||
else if (e.state_key.empty())
|
if (e.type == EventType::RoomMember)
|
||||||
statesdb.del(txn, to_string(e.type));
|
membersdb.del(txn, e.state_key, "");
|
||||||
else
|
else if (e.state_key.empty())
|
||||||
stateskeydb.del(
|
statesdb.del(txn, to_string(e.type));
|
||||||
txn,
|
else
|
||||||
to_string(e.type),
|
stateskeydb.del(
|
||||||
json::object({
|
txn,
|
||||||
{"key", e.state_key},
|
to_string(e.type),
|
||||||
{"id", e.event_id},
|
json::object({
|
||||||
})
|
{"key", e.state_key},
|
||||||
.dump());
|
{"id", e.event_id},
|
||||||
} else if (e.type != EventType::Unsupported) {
|
})
|
||||||
if (e.state_key.empty())
|
.dump());
|
||||||
|
} else if (e.state_key.empty())
|
||||||
statesdb.put(
|
statesdb.put(
|
||||||
txn, to_string(e.type), json(e).dump());
|
txn, to_string(e.type), json(e).dump());
|
||||||
else
|
else
|
||||||
|
@ -680,7 +682,10 @@ private:
|
||||||
return QString::fromStdString(event.state_key);
|
return QString::fromStdString(event.state_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<VerificationCache> verificationCache(const std::string &user_id);
|
std::optional<VerificationCache> verificationCache(const std::string &user_id,
|
||||||
|
lmdb::txn &txn);
|
||||||
|
VerificationStatus verificationStatus_(const std::string &user_id, lmdb::txn &txn);
|
||||||
|
std::optional<UserKeyCache> userKeys_(const std::string &user_id, lmdb::txn &txn);
|
||||||
|
|
||||||
void setNextBatchToken(lmdb::txn &txn, const std::string &token);
|
void setNextBatchToken(lmdb::txn &txn, const std::string &token);
|
||||||
void setNextBatchToken(lmdb::txn &txn, const QString &token);
|
void setNextBatchToken(lmdb::txn &txn, const QString &token);
|
||||||
|
|
|
@ -4,11 +4,9 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QImageReader>
|
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QShortcut>
|
|
||||||
|
|
||||||
#include <mtx/responses.hpp>
|
#include <mtx/responses.hpp>
|
||||||
|
|
||||||
|
|
|
@ -17,10 +17,8 @@
|
||||||
#include <mtx/events/presence.hpp>
|
#include <mtx/events/presence.hpp>
|
||||||
#include <mtx/secret_storage.hpp>
|
#include <mtx/secret_storage.hpp>
|
||||||
|
|
||||||
#include <QFrame>
|
|
||||||
#include <QHBoxLayout>
|
#include <QHBoxLayout>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QPixmap>
|
|
||||||
#include <QPoint>
|
#include <QPoint>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
|
@ -53,6 +53,7 @@ MemberList::roleNames() const
|
||||||
{Mxid, "mxid"},
|
{Mxid, "mxid"},
|
||||||
{DisplayName, "displayName"},
|
{DisplayName, "displayName"},
|
||||||
{AvatarUrl, "avatarUrl"},
|
{AvatarUrl, "avatarUrl"},
|
||||||
|
{Trustlevel, "trustlevel"},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,6 +70,17 @@ MemberList::data(const QModelIndex &index, int role) const
|
||||||
return m_memberList[index.row()].first.display_name;
|
return m_memberList[index.row()].first.display_name;
|
||||||
case AvatarUrl:
|
case AvatarUrl:
|
||||||
return m_memberList[index.row()].second;
|
return m_memberList[index.row()].second;
|
||||||
|
case Trustlevel: {
|
||||||
|
auto stat =
|
||||||
|
cache::verificationStatus(m_memberList[index.row()].first.user_id.toStdString());
|
||||||
|
|
||||||
|
if (!stat)
|
||||||
|
return crypto::Unverified;
|
||||||
|
if (stat->unverified_device_count)
|
||||||
|
return crypto::Unverified;
|
||||||
|
else
|
||||||
|
return stat->user_verified;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ public:
|
||||||
Mxid,
|
Mxid,
|
||||||
DisplayName,
|
DisplayName,
|
||||||
AvatarUrl,
|
AvatarUrl,
|
||||||
|
Trustlevel,
|
||||||
};
|
};
|
||||||
MemberList(const QString &room_id, QObject *parent = nullptr);
|
MemberList(const QString &room_id, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QPainterPath>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
|
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
|
@ -22,14 +24,26 @@ QHash<QString, mtx::crypto::EncryptedFile> infos;
|
||||||
QQuickImageResponse *
|
QQuickImageResponse *
|
||||||
MxcImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
|
MxcImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
|
||||||
{
|
{
|
||||||
auto id_ = id;
|
auto id_ = id;
|
||||||
bool crop = true;
|
bool crop = true;
|
||||||
if (id.endsWith("?scale")) {
|
double radius = 0;
|
||||||
crop = false;
|
|
||||||
id_.remove("?scale");
|
auto queryStart = id.lastIndexOf('?');
|
||||||
|
if (queryStart != -1) {
|
||||||
|
id_ = id.left(queryStart);
|
||||||
|
auto query = id.midRef(queryStart + 1);
|
||||||
|
auto queryBits = query.split('&');
|
||||||
|
|
||||||
|
for (auto b : queryBits) {
|
||||||
|
if (b == "scale") {
|
||||||
|
crop = false;
|
||||||
|
} else if (b.startsWith("radius=")) {
|
||||||
|
radius = b.mid(7).toDouble();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MxcImageResponse *response = new MxcImageResponse(id_, crop, requestedSize);
|
MxcImageResponse *response = new MxcImageResponse(id_, crop, radius, requestedSize);
|
||||||
pool.start(response);
|
pool.start(response);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
@ -53,14 +67,35 @@ MxcImageResponse::run()
|
||||||
}
|
}
|
||||||
emit finished();
|
emit finished();
|
||||||
},
|
},
|
||||||
m_crop);
|
m_crop,
|
||||||
|
m_radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
static QImage
|
||||||
|
clipRadius(QImage img, double radius)
|
||||||
|
{
|
||||||
|
QImage out(img.size(), QImage::Format_ARGB32_Premultiplied);
|
||||||
|
out.fill(Qt::transparent);
|
||||||
|
|
||||||
|
QPainter painter(&out);
|
||||||
|
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||||
|
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||||
|
|
||||||
|
QPainterPath ppath;
|
||||||
|
ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize);
|
||||||
|
|
||||||
|
painter.setClipPath(ppath);
|
||||||
|
painter.drawImage(img.rect(), img);
|
||||||
|
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MxcImageProvider::download(const QString &id,
|
MxcImageProvider::download(const QString &id,
|
||||||
const QSize &requestedSize,
|
const QSize &requestedSize,
|
||||||
std::function<void(QString, QSize, QImage, QString)> then,
|
std::function<void(QString, QSize, QImage, QString)> then,
|
||||||
bool crop)
|
bool crop,
|
||||||
|
double radius)
|
||||||
{
|
{
|
||||||
std::optional<mtx::crypto::EncryptedFile> encryptionInfo;
|
std::optional<mtx::crypto::EncryptedFile> encryptionInfo;
|
||||||
auto temp = infos.find("mxc://" + id);
|
auto temp = infos.find("mxc://" + id);
|
||||||
|
@ -69,12 +104,13 @@ MxcImageProvider::download(const QString &id,
|
||||||
|
|
||||||
if (requestedSize.isValid() && !encryptionInfo) {
|
if (requestedSize.isValid() && !encryptionInfo) {
|
||||||
QString fileName =
|
QString fileName =
|
||||||
QString("%1_%2x%3_%4")
|
QString("%1_%2x%3_%4_radius%5")
|
||||||
.arg(QString::fromUtf8(id.toUtf8().toBase64(QByteArray::Base64UrlEncoding |
|
.arg(QString::fromUtf8(id.toUtf8().toBase64(QByteArray::Base64UrlEncoding |
|
||||||
QByteArray::OmitTrailingEquals)))
|
QByteArray::OmitTrailingEquals)))
|
||||||
.arg(requestedSize.width())
|
.arg(requestedSize.width())
|
||||||
.arg(requestedSize.height())
|
.arg(requestedSize.height())
|
||||||
.arg(crop ? "crop" : "scale");
|
.arg(crop ? "crop" : "scale")
|
||||||
|
.arg(radius);
|
||||||
QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
|
QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
|
||||||
"/media_cache",
|
"/media_cache",
|
||||||
fileName);
|
fileName);
|
||||||
|
@ -86,6 +122,10 @@ MxcImageProvider::download(const QString &id,
|
||||||
image = image.scaled(
|
image = image.scaled(
|
||||||
requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||||
|
|
||||||
|
if (radius != 0) {
|
||||||
|
image = clipRadius(std::move(image), radius);
|
||||||
|
}
|
||||||
|
|
||||||
if (!image.isNull()) {
|
if (!image.isNull()) {
|
||||||
then(id, requestedSize, image, fileInfo.absoluteFilePath());
|
then(id, requestedSize, image, fileInfo.absoluteFilePath());
|
||||||
return;
|
return;
|
||||||
|
@ -100,8 +140,8 @@ MxcImageProvider::download(const QString &id,
|
||||||
opts.method = crop ? "crop" : "scale";
|
opts.method = crop ? "crop" : "scale";
|
||||||
http::client()->get_thumbnail(
|
http::client()->get_thumbnail(
|
||||||
opts,
|
opts,
|
||||||
[fileInfo, requestedSize, then, id](const std::string &res,
|
[fileInfo, requestedSize, radius, then, id](const std::string &res,
|
||||||
mtx::http::RequestErr err) {
|
mtx::http::RequestErr err) {
|
||||||
if (err || res.empty()) {
|
if (err || res.empty()) {
|
||||||
then(id, QSize(), {}, "");
|
then(id, QSize(), {}, "");
|
||||||
|
|
||||||
|
@ -113,6 +153,10 @@ MxcImageProvider::download(const QString &id,
|
||||||
if (!image.isNull()) {
|
if (!image.isNull()) {
|
||||||
image = image.scaled(
|
image = image.scaled(
|
||||||
requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||||
|
|
||||||
|
if (radius != 0) {
|
||||||
|
image = clipRadius(std::move(image), radius);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
image.setText("mxc url", "mxc://" + id);
|
image.setText("mxc url", "mxc://" + id);
|
||||||
if (image.save(fileInfo.absoluteFilePath(), "png"))
|
if (image.save(fileInfo.absoluteFilePath(), "png"))
|
||||||
|
@ -126,8 +170,12 @@ MxcImageProvider::download(const QString &id,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
QString fileName = QString::fromUtf8(id.toUtf8().toBase64(
|
QString fileName =
|
||||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals));
|
QString("%1_radius%2")
|
||||||
|
.arg(QString::fromUtf8(id.toUtf8().toBase64(
|
||||||
|
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)))
|
||||||
|
.arg(radius);
|
||||||
|
|
||||||
QFileInfo fileInfo(
|
QFileInfo fileInfo(
|
||||||
QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
|
QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
|
||||||
"/media_cache",
|
"/media_cache",
|
||||||
|
@ -148,6 +196,11 @@ MxcImageProvider::download(const QString &id,
|
||||||
QImage image = utils::readImage(data);
|
QImage image = utils::readImage(data);
|
||||||
image.setText("mxc url", "mxc://" + id);
|
image.setText("mxc url", "mxc://" + id);
|
||||||
if (!image.isNull()) {
|
if (!image.isNull()) {
|
||||||
|
if (radius != 0) {
|
||||||
|
image =
|
||||||
|
clipRadius(std::move(image), radius);
|
||||||
|
}
|
||||||
|
|
||||||
then(id,
|
then(id,
|
||||||
requestedSize,
|
requestedSize,
|
||||||
image,
|
image,
|
||||||
|
@ -158,6 +211,11 @@ MxcImageProvider::download(const QString &id,
|
||||||
QImage image =
|
QImage image =
|
||||||
utils::readImageFromFile(fileInfo.absoluteFilePath());
|
utils::readImageFromFile(fileInfo.absoluteFilePath());
|
||||||
if (!image.isNull()) {
|
if (!image.isNull()) {
|
||||||
|
if (radius != 0) {
|
||||||
|
image =
|
||||||
|
clipRadius(std::move(image), radius);
|
||||||
|
}
|
||||||
|
|
||||||
then(id,
|
then(id,
|
||||||
requestedSize,
|
requestedSize,
|
||||||
image,
|
image,
|
||||||
|
@ -169,7 +227,7 @@ MxcImageProvider::download(const QString &id,
|
||||||
|
|
||||||
http::client()->download(
|
http::client()->download(
|
||||||
"mxc://" + id.toStdString(),
|
"mxc://" + id.toStdString(),
|
||||||
[fileInfo, requestedSize, then, id, encryptionInfo](
|
[fileInfo, requestedSize, then, id, radius, encryptionInfo](
|
||||||
const std::string &res,
|
const std::string &res,
|
||||||
const std::string &,
|
const std::string &,
|
||||||
const std::string &originalFilename,
|
const std::string &originalFilename,
|
||||||
|
@ -195,6 +253,10 @@ MxcImageProvider::download(const QString &id,
|
||||||
auto data =
|
auto data =
|
||||||
QByteArray(tempData.data(), (int)tempData.size());
|
QByteArray(tempData.data(), (int)tempData.size());
|
||||||
QImage image = utils::readImage(data);
|
QImage image = utils::readImage(data);
|
||||||
|
if (radius != 0) {
|
||||||
|
image = clipRadius(std::move(image), radius);
|
||||||
|
}
|
||||||
|
|
||||||
image.setText("original filename",
|
image.setText("original filename",
|
||||||
QString::fromStdString(originalFilename));
|
QString::fromStdString(originalFilename));
|
||||||
image.setText("mxc url", "mxc://" + id);
|
image.setText("mxc url", "mxc://" + id);
|
||||||
|
@ -205,6 +267,10 @@ MxcImageProvider::download(const QString &id,
|
||||||
|
|
||||||
QImage image =
|
QImage image =
|
||||||
utils::readImageFromFile(fileInfo.absoluteFilePath());
|
utils::readImageFromFile(fileInfo.absoluteFilePath());
|
||||||
|
if (radius != 0) {
|
||||||
|
image = clipRadius(std::move(image), radius);
|
||||||
|
}
|
||||||
|
|
||||||
image.setText("original filename",
|
image.setText("original filename",
|
||||||
QString::fromStdString(originalFilename));
|
QString::fromStdString(originalFilename));
|
||||||
image.setText("mxc url", "mxc://" + id);
|
image.setText("mxc url", "mxc://" + id);
|
||||||
|
|
|
@ -19,10 +19,11 @@ class MxcImageResponse
|
||||||
, public QRunnable
|
, public QRunnable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MxcImageResponse(const QString &id, bool crop, const QSize &requestedSize)
|
MxcImageResponse(const QString &id, bool crop, double radius, const QSize &requestedSize)
|
||||||
: m_id(id)
|
: m_id(id)
|
||||||
, m_requestedSize(requestedSize)
|
, m_requestedSize(requestedSize)
|
||||||
, m_crop(crop)
|
, m_crop(crop)
|
||||||
|
, m_radius(radius)
|
||||||
{
|
{
|
||||||
setAutoDelete(false);
|
setAutoDelete(false);
|
||||||
}
|
}
|
||||||
|
@ -39,6 +40,7 @@ public:
|
||||||
QSize m_requestedSize;
|
QSize m_requestedSize;
|
||||||
QImage m_image;
|
QImage m_image;
|
||||||
bool m_crop;
|
bool m_crop;
|
||||||
|
double m_radius;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MxcImageProvider
|
class MxcImageProvider
|
||||||
|
@ -54,7 +56,8 @@ public slots:
|
||||||
static void download(const QString &id,
|
static void download(const QString &id,
|
||||||
const QSize &requestedSize,
|
const QSize &requestedSize,
|
||||||
std::function<void(QString, QSize, QImage, QString)> then,
|
std::function<void(QString, QSize, QImage, QString)> then,
|
||||||
bool crop = true);
|
bool crop = true,
|
||||||
|
double radius = 0);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QThreadPool pool;
|
QThreadPool pool;
|
||||||
|
|
33
src/Olm.cpp
33
src/Olm.cpp
|
@ -425,6 +425,8 @@ handle_olm_message(const OlmMessage &msg, const UserKeyCache &otherUserDeviceKey
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
nhlog::crypto()->info("Storing secret {}",
|
||||||
|
secret_name->second);
|
||||||
cache::client()->storeSecret(secret_name->second,
|
cache::client()->storeSecret(secret_name->second,
|
||||||
e->content.secret);
|
e->content.secret);
|
||||||
|
|
||||||
|
@ -1110,6 +1112,8 @@ send_encrypted_to_device_messages(const std::map<std::string, std::vector<std::s
|
||||||
const mtx::events::collections::DeviceEvents &event,
|
const mtx::events::collections::DeviceEvents &event,
|
||||||
bool force_new_session)
|
bool force_new_session)
|
||||||
{
|
{
|
||||||
|
static QMap<QPair<std::string, std::string>, qint64> rateLimit;
|
||||||
|
|
||||||
nlohmann::json ev_json = std::visit([](const auto &e) { return json(e); }, event);
|
nlohmann::json ev_json = std::visit([](const auto &e) { return json(e); }, event);
|
||||||
|
|
||||||
std::map<std::string, std::vector<std::string>> keysToQuery;
|
std::map<std::string, std::vector<std::string>> keysToQuery;
|
||||||
|
@ -1162,7 +1166,6 @@ send_encrypted_to_device_messages(const std::map<std::string, std::vector<std::s
|
||||||
|
|
||||||
auto session = cache::getLatestOlmSession(device_curve);
|
auto session = cache::getLatestOlmSession(device_curve);
|
||||||
if (!session || force_new_session) {
|
if (!session || force_new_session) {
|
||||||
static QMap<QPair<std::string, std::string>, qint64> rateLimit;
|
|
||||||
auto currentTime = QDateTime::currentSecsSinceEpoch();
|
auto currentTime = QDateTime::currentSecsSinceEpoch();
|
||||||
if (rateLimit.value(QPair(user, device)) + 60 * 60 * 10 <
|
if (rateLimit.value(QPair(user, device)) + 60 * 60 * 10 <
|
||||||
currentTime) {
|
currentTime) {
|
||||||
|
@ -1318,7 +1321,8 @@ send_encrypted_to_device_messages(const std::map<std::string, std::vector<std::s
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
http::client()->claim_keys(claims, BindPks(pks));
|
if (!claims.one_time_keys.empty())
|
||||||
|
http::client()->claim_keys(claims, BindPks(pks));
|
||||||
|
|
||||||
if (!keysToQuery.empty()) {
|
if (!keysToQuery.empty()) {
|
||||||
mtx::requests::QueryKeys req;
|
mtx::requests::QueryKeys req;
|
||||||
|
@ -1395,9 +1399,25 @@ send_encrypted_to_device_messages(const std::map<std::string, std::vector<std::s
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
deviceKeys[user_id].emplace(device_id, pks);
|
auto currentTime = QDateTime::currentSecsSinceEpoch();
|
||||||
claim_keys.one_time_keys[user.first][device_id] =
|
if (rateLimit.value(QPair(user.first, device_id.get())) +
|
||||||
mtx::crypto::SIGNED_CURVE25519;
|
60 * 60 * 10 <
|
||||||
|
currentTime) {
|
||||||
|
deviceKeys[user_id].emplace(device_id, pks);
|
||||||
|
claim_keys.one_time_keys[user.first][device_id] =
|
||||||
|
mtx::crypto::SIGNED_CURVE25519;
|
||||||
|
|
||||||
|
rateLimit.insert(
|
||||||
|
QPair(user.first, device_id.get()),
|
||||||
|
currentTime);
|
||||||
|
} else {
|
||||||
|
nhlog::crypto()->warn(
|
||||||
|
"Not creating new session with {}:{} "
|
||||||
|
"because of rate limit",
|
||||||
|
user.first,
|
||||||
|
device_id.get());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
nhlog::net()->info("{}", device_id.get());
|
nhlog::net()->info("{}", device_id.get());
|
||||||
nhlog::net()->info(" curve25519 {}", pks.curve25519);
|
nhlog::net()->info(" curve25519 {}", pks.curve25519);
|
||||||
|
@ -1405,7 +1425,8 @@ send_encrypted_to_device_messages(const std::map<std::string, std::vector<std::s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
http::client()->claim_keys(claim_keys, BindPks(deviceKeys));
|
if (!claim_keys.one_time_keys.empty())
|
||||||
|
http::client()->claim_keys(claim_keys, BindPks(deviceKeys));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <QInputDialog>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QMetaType>
|
#include <QMetaType>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
@ -481,6 +482,23 @@ RegisterPage::doUIA(const mtx::user_interactive::Unauthorized &unauthorized)
|
||||||
doRegistrationWithAuth(
|
doRegistrationWithAuth(
|
||||||
mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Dummy{}});
|
mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Dummy{}});
|
||||||
|
|
||||||
|
} else if (current_stage == mtx::user_interactive::auth_types::registration_token) {
|
||||||
|
bool ok;
|
||||||
|
QString token =
|
||||||
|
QInputDialog::getText(this,
|
||||||
|
tr("Registration token"),
|
||||||
|
tr("Please enter a valid registration token."),
|
||||||
|
QLineEdit::Normal,
|
||||||
|
QString(),
|
||||||
|
&ok);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
emit registrationWithAuth(mtx::user_interactive::Auth{
|
||||||
|
session,
|
||||||
|
mtx::user_interactive::auth::RegistrationToken{token.toStdString()}});
|
||||||
|
} else {
|
||||||
|
emit errorOccurred();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// use fallback
|
// use fallback
|
||||||
auto dialog = new dialogs::FallbackAuth(
|
auto dialog = new dialogs::FallbackAuth(
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
#include <QResizeEvent>
|
#include <QResizeEvent>
|
||||||
#include <QScrollArea>
|
#include <QScrollArea>
|
||||||
#include <QScroller>
|
#include <QScroller>
|
||||||
#include <QSettings>
|
|
||||||
#include <QSpinBox>
|
#include <QSpinBox>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
@ -63,7 +62,6 @@ UserSettings::initialize(std::optional<QString> profile)
|
||||||
void
|
void
|
||||||
UserSettings::load(std::optional<QString> profile)
|
UserSettings::load(std::optional<QString> profile)
|
||||||
{
|
{
|
||||||
QSettings settings;
|
|
||||||
tray_ = settings.value("user/window/tray", false).toBool();
|
tray_ = settings.value("user/window/tray", false).toBool();
|
||||||
startInTray_ = settings.value("user/window/start_in_tray", false).toBool();
|
startInTray_ = settings.value("user/window/start_in_tray", false).toBool();
|
||||||
|
|
||||||
|
@ -601,7 +599,6 @@ UserSettings::applyTheme()
|
||||||
void
|
void
|
||||||
UserSettings::save()
|
UserSettings::save()
|
||||||
{
|
{
|
||||||
QSettings settings;
|
|
||||||
settings.beginGroup("user");
|
settings.beginGroup("user");
|
||||||
|
|
||||||
settings.beginGroup("window");
|
settings.beginGroup("window");
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <QFontDatabase>
|
#include <QFontDatabase>
|
||||||
#include <QFrame>
|
#include <QFrame>
|
||||||
#include <QProcessEnvironment>
|
#include <QProcessEnvironment>
|
||||||
|
#include <QSettings>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
@ -107,6 +108,8 @@ public:
|
||||||
static QSharedPointer<UserSettings> instance();
|
static QSharedPointer<UserSettings> instance();
|
||||||
static void initialize(std::optional<QString> profile);
|
static void initialize(std::optional<QString> profile);
|
||||||
|
|
||||||
|
QSettings *qsettings() { return &settings; }
|
||||||
|
|
||||||
enum class Presence
|
enum class Presence
|
||||||
{
|
{
|
||||||
AutomaticPresence,
|
AutomaticPresence,
|
||||||
|
@ -316,6 +319,8 @@ private:
|
||||||
QString homeserver_;
|
QString homeserver_;
|
||||||
QStringList hiddenTags_;
|
QStringList hiddenTags_;
|
||||||
|
|
||||||
|
QSettings settings;
|
||||||
|
|
||||||
static QSharedPointer<UserSettings> instance_;
|
static QSharedPointer<UserSettings> instance_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,10 @@ ImageOverlay::ImageOverlay(QPixmap image, QWidget *parent)
|
||||||
setAttribute(Qt::WA_TranslucentBackground, true);
|
setAttribute(Qt::WA_TranslucentBackground, true);
|
||||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||||
setWindowState(Qt::WindowFullScreen);
|
setWindowState(Qt::WindowFullScreen);
|
||||||
|
close_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Escape), this);
|
||||||
|
|
||||||
connect(this, SIGNAL(closing()), this, SLOT(close()));
|
connect(close_shortcut_, &QShortcut::activated, this, &ImageOverlay::closing);
|
||||||
|
connect(this, &ImageOverlay::closing, this, &ImageOverlay::close);
|
||||||
|
|
||||||
raise();
|
raise();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
|
#include <QShortcut>
|
||||||
|
|
||||||
namespace dialogs {
|
namespace dialogs {
|
||||||
|
|
||||||
|
@ -32,5 +33,6 @@ private:
|
||||||
QRect content_;
|
QRect content_;
|
||||||
QRect close_button_;
|
QRect close_button_;
|
||||||
QRect save_button_;
|
QRect save_button_;
|
||||||
|
QShortcut *close_shortcut_;
|
||||||
};
|
};
|
||||||
} // dialogs
|
} // dialogs
|
||||||
|
|
|
@ -310,7 +310,7 @@ qml_mtx_events::fromRoomEventType(qml_mtx_events::EventType t)
|
||||||
return mtx::events::EventType::RoomMessage;
|
return mtx::events::EventType::RoomMessage;
|
||||||
//! m.image_pack, currently im.ponies.room_emotes
|
//! m.image_pack, currently im.ponies.room_emotes
|
||||||
case qml_mtx_events::ImagePackInRoom:
|
case qml_mtx_events::ImagePackInRoom:
|
||||||
return mtx::events::EventType::ImagePackRooms;
|
return mtx::events::EventType::ImagePackInRoom;
|
||||||
//! m.image_pack, currently im.ponies.user_emotes
|
//! m.image_pack, currently im.ponies.user_emotes
|
||||||
case qml_mtx_events::ImagePackInAccountData:
|
case qml_mtx_events::ImagePackInAccountData:
|
||||||
return mtx::events::EventType::ImagePackInAccountData;
|
return mtx::events::EventType::ImagePackInAccountData;
|
||||||
|
@ -418,6 +418,14 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
|
||||||
&events,
|
&events,
|
||||||
&EventStore::enableKeyRequests);
|
&EventStore::enableKeyRequests);
|
||||||
|
|
||||||
|
connect(this, &TimelineModel::encryptionChanged, this, &TimelineModel::trustlevelChanged);
|
||||||
|
connect(
|
||||||
|
this, &TimelineModel::roomMemberCountChanged, this, &TimelineModel::trustlevelChanged);
|
||||||
|
connect(cache::client(),
|
||||||
|
&Cache::verificationStatusChanged,
|
||||||
|
this,
|
||||||
|
&TimelineModel::trustlevelChanged);
|
||||||
|
|
||||||
showEventTimer.callOnTimeout(this, &TimelineModel::scrollTimerEvent);
|
showEventTimer.callOnTimeout(this, &TimelineModel::scrollTimerEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1993,6 +2001,15 @@ TimelineModel::roomTopic() const
|
||||||
QString::fromStdString(info[room_id_].topic).toHtmlEscaped()));
|
QString::fromStdString(info[room_id_].topic).toHtmlEscaped()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
crypto::Trust
|
||||||
|
TimelineModel::trustlevel() const
|
||||||
|
{
|
||||||
|
if (!isEncrypted_)
|
||||||
|
return crypto::Trust::Unverified;
|
||||||
|
|
||||||
|
return cache::client()->roomVerificationStatus(room_id_.toStdString());
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
TimelineModel::roomMemberCount() const
|
TimelineModel::roomMemberCount() const
|
||||||
{
|
{
|
||||||
|
|
|
@ -175,6 +175,7 @@ class TimelineModel : public QAbstractListModel
|
||||||
Q_PROPERTY(int roomMemberCount READ roomMemberCount NOTIFY roomMemberCountChanged)
|
Q_PROPERTY(int roomMemberCount READ roomMemberCount NOTIFY roomMemberCountChanged)
|
||||||
Q_PROPERTY(bool isEncrypted READ isEncrypted NOTIFY encryptionChanged)
|
Q_PROPERTY(bool isEncrypted READ isEncrypted NOTIFY encryptionChanged)
|
||||||
Q_PROPERTY(bool isSpace READ isSpace CONSTANT)
|
Q_PROPERTY(bool isSpace READ isSpace CONSTANT)
|
||||||
|
Q_PROPERTY(int trustlevel READ trustlevel NOTIFY trustlevelChanged)
|
||||||
Q_PROPERTY(InputBar *input READ input CONSTANT)
|
Q_PROPERTY(InputBar *input READ input CONSTANT)
|
||||||
Q_PROPERTY(Permissions *permissions READ permissions NOTIFY permissionsChanged)
|
Q_PROPERTY(Permissions *permissions READ permissions NOTIFY permissionsChanged)
|
||||||
|
|
||||||
|
@ -287,6 +288,7 @@ public:
|
||||||
DescInfo lastMessage() const { return lastMessage_; }
|
DescInfo lastMessage() const { return lastMessage_; }
|
||||||
bool isSpace() const { return isSpace_; }
|
bool isSpace() const { return isSpace_; }
|
||||||
bool isEncrypted() const { return isEncrypted_; }
|
bool isEncrypted() const { return isEncrypted_; }
|
||||||
|
crypto::Trust trustlevel() const;
|
||||||
int roomMemberCount() const;
|
int roomMemberCount() const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
@ -372,6 +374,7 @@ signals:
|
||||||
void updateFlowEventId(std::string event_id);
|
void updateFlowEventId(std::string event_id);
|
||||||
|
|
||||||
void encryptionChanged();
|
void encryptionChanged();
|
||||||
|
void trustlevelChanged();
|
||||||
void roomNameChanged();
|
void roomNameChanged();
|
||||||
void plainRoomNameChanged();
|
void plainRoomNameChanged();
|
||||||
void roomTopicChanged();
|
void roomTopicChanged();
|
||||||
|
|
|
@ -375,10 +375,12 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
TimelineViewManager::openRoomMembers(QString room_id)
|
TimelineViewManager::openRoomMembers(TimelineModel *room)
|
||||||
{
|
{
|
||||||
MemberList *memberList = new MemberList(room_id, this);
|
if (!room)
|
||||||
emit openRoomMembersDialog(memberList);
|
return;
|
||||||
|
MemberList *memberList = new MemberList(room->roomId(), this);
|
||||||
|
emit openRoomMembersDialog(memberList, room);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -66,7 +66,7 @@ public:
|
||||||
Q_INVOKABLE QString userPresence(QString id) const;
|
Q_INVOKABLE QString userPresence(QString id) const;
|
||||||
Q_INVOKABLE QString userStatus(QString id) const;
|
Q_INVOKABLE QString userStatus(QString id) const;
|
||||||
|
|
||||||
Q_INVOKABLE void openRoomMembers(QString room_id);
|
Q_INVOKABLE void openRoomMembers(TimelineModel *room);
|
||||||
Q_INVOKABLE void openRoomSettings(QString room_id);
|
Q_INVOKABLE void openRoomSettings(QString room_id);
|
||||||
Q_INVOKABLE void openInviteUsers(QString roomId);
|
Q_INVOKABLE void openInviteUsers(QString roomId);
|
||||||
Q_INVOKABLE void openGlobalUserProfile(QString userId);
|
Q_INVOKABLE void openGlobalUserProfile(QString userId);
|
||||||
|
@ -92,7 +92,7 @@ signals:
|
||||||
void focusChanged();
|
void focusChanged();
|
||||||
void focusInput();
|
void focusInput();
|
||||||
void openImageOverlayInternalCb(QString eventId, QImage img);
|
void openImageOverlayInternalCb(QString eventId, QImage img);
|
||||||
void openRoomMembersDialog(MemberList *members);
|
void openRoomMembersDialog(MemberList *members, TimelineModel *room);
|
||||||
void openRoomSettingsDialog(RoomSettings *settings);
|
void openRoomSettingsDialog(RoomSettings *settings);
|
||||||
void openInviteUsersDialog(InviteesModel *invitees);
|
void openInviteUsersDialog(InviteesModel *invitees);
|
||||||
void openProfile(UserProfile *profile);
|
void openProfile(UserProfile *profile);
|
||||||
|
|
Loading…
Reference in a new issue