mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-26 04:58:49 +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)
|
||||
|
||||
# Add Qt binaries to path
|
||||
PATH=/usr/local/opt/qt/bin/:${PATH}
|
||||
PATH=/usr/local/opt/qt@5/bin/:${PATH}
|
||||
|
||||
( cd build
|
||||
# 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(
|
||||
MatrixClient
|
||||
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_TESTS OFF CACHE INTERNAL "")
|
||||
|
@ -676,7 +676,7 @@ if(USE_BUNDLED_COEURL)
|
|||
FetchContent_Declare(
|
||||
coeurl
|
||||
GIT_REPOSITORY https://nheko.im/Nheko-Reborn/coeurl.git
|
||||
GIT_TAG e9010d1ce14e7163d1cb5407ed27b23303781796
|
||||
GIT_TAG 3901507db25cf3f9364b58cd8c7880640900c992
|
||||
)
|
||||
FetchContent_MakeAvailable(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
|
||||
# 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)
|
||||
and mtxclient (needs to be build separately).
|
||||
|
|
|
@ -152,7 +152,7 @@ modules:
|
|||
- -Ddefault_library=static
|
||||
name: coeurl
|
||||
sources:
|
||||
- commit: 417821a07cfe4429b08a2efed5e480a498087afd
|
||||
- commit: 3901507db25cf3f9364b58cd8c7880640900c992
|
||||
type: git
|
||||
url: https://nheko.im/nheko-reborn/coeurl.git
|
||||
- config-opts:
|
||||
|
@ -163,7 +163,7 @@ modules:
|
|||
buildsystem: cmake-ninja
|
||||
name: mtxclient
|
||||
sources:
|
||||
- commit: bcf363cb5e6c423f40c96123e227bc8c5f6d6f80
|
||||
- commit: deb51ef1d6df870098069312f0a1999550e1eb85
|
||||
type: git
|
||||
url: https://github.com/Nheko-Reborn/mtxclient.git
|
||||
- config-opts:
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import "./ui"
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick 2.6
|
||||
import QtQuick.Controls 2.3
|
||||
import im.nheko 1.0
|
||||
|
@ -21,7 +20,7 @@ Rectangle {
|
|||
|
||||
width: 48
|
||||
height: 48
|
||||
radius: Settings.avatarCircles ? height / 2 : 3
|
||||
radius: Settings.avatarCircles ? height / 2 : height / 8
|
||||
color: Nheko.colors.alternateBase
|
||||
Component.onCompleted: {
|
||||
mouseArea.clicked.connect(clicked);
|
||||
|
@ -50,8 +49,7 @@ Rectangle {
|
|||
smooth: true
|
||||
sourceSize.width: avatar.width
|
||||
sourceSize.height: avatar.height
|
||||
layer.enabled: true
|
||||
source: avatar.url + ((avatar.crop || !avatar.url) ? "" : "?scale")
|
||||
source: avatar.url ? (avatar.url + "?radius=" + (Settings.avatarCircles ? 100.0 : 25.0) + ((avatar.crop) ? "" : "&scale")) : ""
|
||||
|
||||
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 {
|
||||
|
@ -85,7 +71,7 @@ Rectangle {
|
|||
visible: !!userid
|
||||
height: avatar.height / 6
|
||||
width: height
|
||||
radius: Settings.avatarCircles ? height / 2 : height / 4
|
||||
radius: Settings.avatarCircles ? height / 2 : height / 8
|
||||
color: {
|
||||
switch (TimelineManager.userPresence(userid)) {
|
||||
case "online":
|
||||
|
|
|
@ -68,6 +68,7 @@ Popup {
|
|||
isOnlyEmoji: modelData.isOnlyEmoji ?? false
|
||||
userId: modelData.userId ?? ""
|
||||
userName: modelData.userName ?? ""
|
||||
encryptionError: modelData.encryptionError ?? ""
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
|
@ -85,6 +86,9 @@ Popup {
|
|||
} else if (event.key == Qt.Key_Down && completerPopup.opened) {
|
||||
event.accepted = true;
|
||||
completerPopup.down();
|
||||
} else if (event.key == Qt.Key_Tab && completerPopup.opened) {
|
||||
event.accepted = true;
|
||||
completerPopup.down();
|
||||
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
|
||||
completerPopup.finishCompletion();
|
||||
event.accepted = true;
|
||||
|
|
|
@ -6,7 +6,6 @@ import "./delegates"
|
|||
import "./emoji"
|
||||
import "./ui"
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.2
|
||||
|
|
|
@ -45,6 +45,9 @@ Popup {
|
|||
} else if (event.key == Qt.Key_Down && completerPopup.opened) {
|
||||
event.accepted = true;
|
||||
completerPopup.down();
|
||||
} else if (event.key == Qt.Key_Tab && completerPopup.opened) {
|
||||
event.accepted = true;
|
||||
completerPopup.down();
|
||||
} else if (event.matches(StandardKey.InsertParagraphSeparator)) {
|
||||
completerPopup.finishCompletion();
|
||||
event.accepted = true;
|
||||
|
|
|
@ -45,6 +45,7 @@ Rectangle {
|
|||
isOnlyEmoji: modelData.isOnlyEmoji ?? false
|
||||
userId: modelData.userId ?? ""
|
||||
userName: modelData.userName ?? ""
|
||||
encryptionError: modelData.encryptionError ?? ""
|
||||
}
|
||||
|
||||
ImageButton {
|
||||
|
|
|
@ -33,8 +33,9 @@ Page {
|
|||
|
||||
Connections {
|
||||
function onCurrentRoomChanged() {
|
||||
if (Rooms.currentRoom)
|
||||
roomlist.positionViewAtIndex(Rooms.roomidToIndex(Rooms.currentRoom.roomId), ListView.Contain);
|
||||
console.log("Test" + Rooms.currentRoom.roomId + " " + Rooms.roomidToIndex(Rooms.currentRoom.roomId));
|
||||
|
||||
}
|
||||
|
||||
target: Rooms
|
||||
|
@ -190,7 +191,12 @@ Page {
|
|||
|
||||
TapHandler {
|
||||
margin: -Nheko.paddingSmall
|
||||
onSingleTapped: Rooms.setCurrentRoom(roomId)
|
||||
onSingleTapped: {
|
||||
if (!Rooms.currentRoom || Rooms.currentRoom.roomId !== roomId)
|
||||
Rooms.setCurrentRoom(roomId);
|
||||
else
|
||||
Rooms.resetCurrentRoom();
|
||||
}
|
||||
onLongPressed: {
|
||||
if (!isInvite)
|
||||
roomContextMenu.show(roomId, tags);
|
||||
|
|
|
@ -13,6 +13,7 @@ ApplicationWindow {
|
|||
id: roomMembersRoot
|
||||
|
||||
property MemberList members
|
||||
property Room room
|
||||
|
||||
title: qsTr("Members of %1").arg(members.roomName)
|
||||
height: 650
|
||||
|
@ -83,9 +84,14 @@ ApplicationWindow {
|
|||
}
|
||||
|
||||
delegate: RowLayout {
|
||||
id: del
|
||||
|
||||
width: ListView.view.width
|
||||
spacing: Nheko.paddingMedium
|
||||
|
||||
Avatar {
|
||||
id: avatar
|
||||
|
||||
width: Nheko.avatarSize
|
||||
height: Nheko.avatarSize
|
||||
userid: model.mxid
|
||||
|
@ -97,16 +103,18 @@ ApplicationWindow {
|
|||
ColumnLayout {
|
||||
spacing: Nheko.paddingSmall
|
||||
|
||||
Label {
|
||||
text: model.displayName
|
||||
ElidedLabel {
|
||||
fullText: model.displayName
|
||||
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 {
|
||||
text: model.mxid
|
||||
ElidedLabel {
|
||||
fullText: model.mxid
|
||||
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 {
|
||||
|
@ -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 {
|
||||
|
|
|
@ -15,8 +15,8 @@ ApplicationWindow {
|
|||
|
||||
property var roomSettings
|
||||
|
||||
minimumWidth: 420
|
||||
minimumHeight: 650
|
||||
minimumWidth: 450
|
||||
minimumHeight: 680
|
||||
palette: Nheko.colors
|
||||
color: Nheko.colors.window
|
||||
modality: Qt.NonModal
|
||||
|
|
|
@ -8,7 +8,6 @@ import "./dialogs"
|
|||
import "./emoji"
|
||||
import "./voip"
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Layouts 1.3
|
||||
|
@ -153,10 +152,10 @@ Page {
|
|||
packSet.show();
|
||||
}
|
||||
|
||||
function onOpenRoomMembersDialog(members) {
|
||||
function onOpenRoomMembersDialog(members, room) {
|
||||
var membersDialog = roomMembersComponent.createObject(timelineRoot, {
|
||||
"members": members,
|
||||
"roomName": Rooms.currentRoom.roomName
|
||||
"room": room
|
||||
});
|
||||
membersDialog.show();
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import "./emoji"
|
|||
import "./ui"
|
||||
import "./voip"
|
||||
import Qt.labs.platform 1.1 as Platform
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.5
|
||||
import QtQuick.Layouts 1.3
|
||||
|
@ -85,11 +84,16 @@ Item {
|
|||
target: timelineView
|
||||
}
|
||||
|
||||
MessageView {
|
||||
Loader {
|
||||
active: room || roomPreview
|
||||
Layout.fillWidth: true
|
||||
|
||||
sourceComponent: MessageView {
|
||||
implicitHeight: msgView.height - typingIndicator.height
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Loader {
|
||||
source: CallManager.isOnCall && CallManager.callType != CallType.VOICE ? "voip/VideoCall.qml" : ""
|
||||
onLoaded: TimelineManager.setVideoCallItem()
|
||||
|
|
|
@ -15,6 +15,8 @@ Rectangle {
|
|||
property string roomName: room ? room.roomName : qsTr("No room selected")
|
||||
property string avatarUrl: room ? room.roomAvatarUrl : ""
|
||||
property string roomTopic: room ? room.roomTopic : ""
|
||||
property bool isEncrypted: room ? room.isEncrypted : false
|
||||
property int trustlevel: room ? room.trustlevel : Crypto.Unverified
|
||||
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: topLayout.height + Nheko.paddingMedium * 2
|
||||
|
@ -92,11 +94,33 @@ Rectangle {
|
|||
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 {
|
||||
id: roomOptionsButton
|
||||
|
||||
visible: !!room
|
||||
Layout.column: 3
|
||||
Layout.column: 4
|
||||
Layout.row: 0
|
||||
Layout.rowSpan: 2
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
@ -116,7 +140,7 @@ Rectangle {
|
|||
|
||||
Platform.MenuItem {
|
||||
text: qsTr("Members")
|
||||
onTriggered: TimelineManager.openRoomMembers(room.roomId)
|
||||
onTriggered: TimelineManager.openRoomMembers(room)
|
||||
}
|
||||
|
||||
Platform.MenuItem {
|
||||
|
|
|
@ -171,7 +171,7 @@ ApplicationWindow {
|
|||
}
|
||||
|
||||
MatrixText {
|
||||
text: qsTr("Attrbution")
|
||||
text: qsTr("Attribution")
|
||||
}
|
||||
|
||||
MatrixTextField {
|
||||
|
|
288
src/Cache.cpp
288
src/Cache.cpp
|
@ -114,7 +114,13 @@ ro_txn(lmdb::env &env)
|
|||
txn = lmdb::txn::begin(env, nullptr, MDB_RDONLY);
|
||||
reuse_counter = 0;
|
||||
} else if (reuse_counter > 0) {
|
||||
try {
|
||||
txn.renew();
|
||||
} catch (...) {
|
||||
txn.abort();
|
||||
txn = lmdb::txn::begin(env, nullptr, MDB_RDONLY);
|
||||
reuse_counter = 0;
|
||||
}
|
||||
}
|
||||
reuse_counter++;
|
||||
|
||||
|
@ -290,6 +296,8 @@ Cache::setup()
|
|||
|
||||
// What rooms are encrypted
|
||||
encryptedRooms_ = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE);
|
||||
[[maybe_unused]] auto verificationDb = getVerificationDb(txn);
|
||||
[[maybe_unused]] auto userKeysDb = getUserKeysDb(txn);
|
||||
|
||||
txn.commit();
|
||||
|
||||
|
@ -720,20 +728,35 @@ Cache::storeSecret(const std::string name, const std::string secret)
|
|||
{
|
||||
auto settings = UserSettings::instance();
|
||||
auto job = new QKeychain::WritePasswordJob(QCoreApplication::applicationName());
|
||||
job->setAutoDelete(true);
|
||||
job->setInsecureFallback(true);
|
||||
job->setKey("matrix." +
|
||||
QString(QCryptographicHash::hash(settings->profile().toUtf8(),
|
||||
QCryptographicHash::Sha256)) +
|
||||
"." + name.c_str());
|
||||
job->setSettings(UserSettings::instance()->qsettings());
|
||||
|
||||
job->setKey(
|
||||
"matrix." +
|
||||
QString(QCryptographicHash::hash(settings->profile().toUtf8(), QCryptographicHash::Sha256)
|
||||
.toBase64()) +
|
||||
"." + QString::fromStdString(name));
|
||||
|
||||
job->setTextData(QString::fromStdString(secret));
|
||||
QObject::connect(job, &QKeychain::Job::finished, job, [name, this](QKeychain::Job *job) {
|
||||
|
||||
QObject::connect(
|
||||
job,
|
||||
&QKeychain::WritePasswordJob::finished,
|
||||
this,
|
||||
[name, this](QKeychain::Job *job) {
|
||||
if (job->error()) {
|
||||
nhlog::db()->warn(
|
||||
"Storing secret '{}' failed: {}", name, job->errorString().toStdString());
|
||||
nhlog::db()->warn("Storing secret '{}' failed: {}",
|
||||
name,
|
||||
job->errorString().toStdString());
|
||||
} else {
|
||||
emit secretChanged(name);
|
||||
// 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();
|
||||
}
|
||||
|
||||
|
@ -744,10 +767,14 @@ Cache::deleteSecret(const std::string name)
|
|||
QKeychain::DeletePasswordJob job(QCoreApplication::applicationName());
|
||||
job.setAutoDelete(false);
|
||||
job.setInsecureFallback(true);
|
||||
job.setKey("matrix." +
|
||||
QString(QCryptographicHash::hash(settings->profile().toUtf8(),
|
||||
QCryptographicHash::Sha256)) +
|
||||
"." + name.c_str());
|
||||
job.setSettings(UserSettings::instance()->qsettings());
|
||||
|
||||
job.setKey(
|
||||
"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
|
||||
// time!
|
||||
QEventLoop loop;
|
||||
|
@ -765,10 +792,14 @@ Cache::secret(const std::string name)
|
|||
QKeychain::ReadPasswordJob job(QCoreApplication::applicationName());
|
||||
job.setAutoDelete(false);
|
||||
job.setInsecureFallback(true);
|
||||
job.setKey("matrix." +
|
||||
QString(QCryptographicHash::hash(settings->profile().toUtf8(),
|
||||
QCryptographicHash::Sha256)) +
|
||||
"." + name.c_str());
|
||||
job.setSettings(UserSettings::instance()->qsettings());
|
||||
|
||||
job.setKey(
|
||||
"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
|
||||
// time!
|
||||
QEventLoop loop;
|
||||
|
@ -838,6 +869,9 @@ Cache::setNextBatchToken(lmdb::txn &txn, const QString &token)
|
|||
bool
|
||||
Cache::isInitialized()
|
||||
{
|
||||
if (!env_.handle())
|
||||
return false;
|
||||
|
||||
auto txn = ro_txn(env_);
|
||||
std::string_view token;
|
||||
|
||||
|
@ -1564,6 +1598,8 @@ RoomInfo
|
|||
Cache::singleRoomInfo(const std::string &room_id)
|
||||
{
|
||||
auto txn = ro_txn(env_);
|
||||
|
||||
try {
|
||||
auto statesdb = getStatesDb(txn, room_id);
|
||||
|
||||
std::string_view data;
|
||||
|
@ -1584,6 +1620,10 @@ Cache::singleRoomInfo(const std::string &room_id)
|
|||
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();
|
||||
}
|
||||
|
@ -3541,6 +3581,44 @@ Cache::roomMembers(const std::string &room_id)
|
|||
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>>
|
||||
Cache::getMembersWithKeys(const std::string &room_id, bool verified_only)
|
||||
{
|
||||
|
@ -3722,11 +3800,17 @@ from_json(const json &j, UserKeyCache &info)
|
|||
|
||||
std::optional<UserKeyCache>
|
||||
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;
|
||||
|
||||
try {
|
||||
auto txn = ro_txn(env_);
|
||||
auto db = getUserKeysDb(txn);
|
||||
auto res = db.get(txn, user_id, keys);
|
||||
|
||||
|
@ -3735,7 +3819,8 @@ Cache::userKeys(const std::string &user_id)
|
|||
} else {
|
||||
return {};
|
||||
}
|
||||
} catch (std::exception &) {
|
||||
} catch (std::exception &e) {
|
||||
nhlog::db()->error("Failed to retrieve user keys for {}: {}", user_id, e.what());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
@ -3770,8 +3855,14 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query
|
|||
auto last_changed = updateToWrite.last_changed;
|
||||
// skip if we are tracking this and expect it to be up to date with the last
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (!updateToWrite.master_keys.keys.empty() &&
|
||||
update.master_keys.keys != updateToWrite.master_keys.keys) {
|
||||
|
@ -3819,9 +3910,44 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &[key_id, key] : device_keys.keys) {
|
||||
(void)key_id;
|
||||
|
@ -3830,6 +3956,7 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query
|
|||
updateToWrite.seen_device_ids.insert(device_id);
|
||||
}
|
||||
}
|
||||
updateToWrite.updated_at = sync_token;
|
||||
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);
|
||||
|
||||
std::string_view oldKeys;
|
||||
|
||||
UserKeyCache cacheEntry;
|
||||
auto res = db.get(txn, user, oldKeys);
|
||||
|
||||
if (!res)
|
||||
continue;
|
||||
|
||||
auto cacheEntry =
|
||||
json::parse(std::string_view(oldKeys.data(), oldKeys.size())).get<UserKeyCache>();
|
||||
if (res) {
|
||||
cacheEntry = json::parse(std::string_view(oldKeys.data(), oldKeys.size()))
|
||||
.get<UserKeyCache>();
|
||||
}
|
||||
cacheEntry.last_changed = sync_token;
|
||||
|
||||
db.put(txn, user, json(cacheEntry).dump());
|
||||
|
||||
query.device_keys[user] = {};
|
||||
|
@ -3915,35 +4043,46 @@ void
|
|||
Cache::query_keys(const std::string &user_id,
|
||||
std::function<void(const UserKeyCache &, mtx::http::RequestErr)> cb)
|
||||
{
|
||||
auto cache_ = cache::userKeys(user_id);
|
||||
mtx::requests::QueryKeys req;
|
||||
std::string last_changed;
|
||||
{
|
||||
auto txn = ro_txn(env_);
|
||||
auto cache_ = userKeys_(user_id, txn);
|
||||
|
||||
if (cache_.has_value()) {
|
||||
if (!cache_->updated_at.empty() && cache_->updated_at == cache_->last_changed) {
|
||||
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);
|
||||
|
||||
mtx::requests::QueryKeys req;
|
||||
req.device_keys[user_id] = {};
|
||||
|
||||
std::string last_changed;
|
||||
if (cache_)
|
||||
last_changed = cache_->last_changed;
|
||||
req.token = last_changed;
|
||||
}
|
||||
|
||||
// use context object so that we can disconnect again
|
||||
QObject *context{new QObject(this)};
|
||||
QObject::connect(this,
|
||||
QObject::connect(
|
||||
this,
|
||||
&Cache::verificationStatusChanged,
|
||||
context,
|
||||
[cb, user_id, context_ = context](std::string updated_user) mutable {
|
||||
[cb, user_id, context_ = context, this](std::string updated_user) mutable {
|
||||
if (user_id == updated_user) {
|
||||
context_->deleteLater();
|
||||
auto keys = cache::userKeys(user_id);
|
||||
auto txn = ro_txn(env_);
|
||||
auto keys = this->userKeys_(user_id, txn);
|
||||
cb(keys.value_or(UserKeyCache{}), {});
|
||||
}
|
||||
});
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
http::client()->query_keys(
|
||||
req,
|
||||
|
@ -3971,16 +4110,15 @@ to_json(json &j, const VerificationCache &info)
|
|||
void
|
||||
from_json(const json &j, VerificationCache &info)
|
||||
{
|
||||
info.device_verified = j.at("device_verified").get<std::vector<std::string>>();
|
||||
info.device_blocked = j.at("device_blocked").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::set<std::string>>();
|
||||
}
|
||||
|
||||
std::optional<VerificationCache>
|
||||
Cache::verificationCache(const std::string &user_id)
|
||||
Cache::verificationCache(const std::string &user_id, lmdb::txn &txn)
|
||||
{
|
||||
std::string_view verifiedVal;
|
||||
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
auto db = getVerificationDb(txn);
|
||||
|
||||
try {
|
||||
|
@ -4000,6 +4138,7 @@ Cache::verificationCache(const std::string &user_id)
|
|||
void
|
||||
Cache::markDeviceVerified(const std::string &user_id, const std::string &key)
|
||||
{
|
||||
{
|
||||
std::string_view val;
|
||||
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
|
@ -4016,11 +4155,12 @@ Cache::markDeviceVerified(const std::string &user_id, const std::string &key)
|
|||
if (device == key)
|
||||
return;
|
||||
|
||||
verified_state.device_verified.push_back(key);
|
||||
verified_state.device_verified.insert(key);
|
||||
db.put(txn, user_id, json(verified_state).dump());
|
||||
txn.commit();
|
||||
} catch (std::exception &) {
|
||||
}
|
||||
}
|
||||
|
||||
const auto local_user = utils::localUser().toStdString();
|
||||
std::map<std::string, VerificationStatus> tmp;
|
||||
|
@ -4057,11 +4197,7 @@ Cache::markDeviceUnverified(const std::string &user_id, const std::string &key)
|
|||
verified_state = json::parse(val);
|
||||
}
|
||||
|
||||
verified_state.device_verified.erase(
|
||||
std::remove(verified_state.device_verified.begin(),
|
||||
verified_state.device_verified.end(),
|
||||
key),
|
||||
verified_state.device_verified.end());
|
||||
verified_state.device_verified.erase(key);
|
||||
|
||||
db.put(txn, user_id, json(verified_state).dump());
|
||||
txn.commit();
|
||||
|
@ -4090,6 +4226,13 @@ Cache::markDeviceUnverified(const std::string &user_id, const std::string &key)
|
|||
|
||||
VerificationStatus
|
||||
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);
|
||||
if (verification_storage.status.count(user_id))
|
||||
|
@ -4097,7 +4240,12 @@ Cache::verificationStatus(const std::string &user_id)
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -4105,12 +4253,10 @@ Cache::verificationStatus(const std::string &user_id)
|
|||
|
||||
crypto::Trust trustlevel = crypto::Trust::Unverified;
|
||||
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;
|
||||
}
|
||||
|
||||
verification_storage.status[user_id] = status;
|
||||
|
||||
auto verifyAtLeastOneSig = [](const auto &toVerif,
|
||||
const std::map<std::string, std::string> &keys,
|
||||
const std::string &keyOwner) {
|
||||
|
@ -4128,6 +4274,16 @@ Cache::verificationStatus(const std::string &user_id)
|
|||
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 {
|
||||
// for local user verify this device_key -> our master_key -> our self_signing_key
|
||||
// -> 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
|
||||
// key and their master key
|
||||
auto ourKeys = userKeys(local_user);
|
||||
auto theirKeys = userKeys(user_id);
|
||||
if (!ourKeys || !theirKeys)
|
||||
auto ourKeys = userKeys_(local_user, txn);
|
||||
auto theirKeys = userKeys_(user_id, txn);
|
||||
if (theirKeys)
|
||||
status.no_keys = false;
|
||||
|
||||
if (!ourKeys || !theirKeys) {
|
||||
verification_storage.status[user_id] = status;
|
||||
return status;
|
||||
}
|
||||
|
||||
// Update verified devices count to count without cross-signing
|
||||
updateUnverifiedDevices(theirKeys->device_keys);
|
||||
|
||||
if (!mtx::crypto::ed25519_verify_signature(
|
||||
olm::client()->identity_keys().ed25519,
|
||||
json(ourKeys->master_keys),
|
||||
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;
|
||||
}
|
||||
|
||||
auto master_keys = ourKeys->master_keys.keys;
|
||||
|
||||
|
@ -4162,14 +4328,17 @@ Cache::verificationStatus(const std::string &user_id)
|
|||
trustlevel = crypto::Trust::Verified;
|
||||
else if (!theirKeys->master_key_changed)
|
||||
trustlevel = crypto::Trust::TOFU;
|
||||
else
|
||||
else {
|
||||
verification_storage.status[user_id] = status;
|
||||
return status;
|
||||
}
|
||||
|
||||
master_keys = theirKeys->master_keys.keys;
|
||||
}
|
||||
|
||||
status.user_verified = trustlevel;
|
||||
|
||||
verification_storage.status[user_id] = status;
|
||||
if (!verifyAtLeastOneSig(theirKeys->self_signing_keys, master_keys, user_id))
|
||||
return status;
|
||||
|
||||
|
@ -4180,16 +4349,19 @@ Cache::verificationStatus(const std::string &user_id)
|
|||
device_key.keys.at("curve25519:" + device_key.device_id);
|
||||
if (verifyAtLeastOneSig(
|
||||
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;
|
||||
}
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
updateUnverifiedDevices(theirKeys->device_keys);
|
||||
verification_storage.status[user_id] = 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,9 +112,13 @@ struct VerificationStatus
|
|||
//! True, if the users master key is verified
|
||||
crypto::Trust user_verified = crypto::Trust::Unverified;
|
||||
//! 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
|
||||
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
|
||||
|
@ -154,9 +158,9 @@ from_json(const nlohmann::json &j, UserKeyCache &info);
|
|||
struct VerificationCache
|
||||
{
|
||||
//! 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
|
||||
std::vector<std::string> device_blocked;
|
||||
std::set<std::string> device_blocked;
|
||||
};
|
||||
|
||||
void
|
||||
|
|
|
@ -46,7 +46,6 @@ public:
|
|||
std::string statusMessage(const std::string &user_id);
|
||||
|
||||
// user cache stores user keys
|
||||
std::optional<UserKeyCache> userKeys(const std::string &user_id);
|
||||
std::map<std::string, std::optional<UserKeyCache>> getMembersWithKeys(
|
||||
const std::string &room_id,
|
||||
bool verified_only);
|
||||
|
@ -63,9 +62,11 @@ public:
|
|||
std::function<void(const UserKeyCache &, mtx::http::RequestErr)> cb);
|
||||
|
||||
// device & user verification cache
|
||||
std::optional<UserKeyCache> userKeys(const std::string &user_id);
|
||||
VerificationStatus verificationStatus(const std::string &user_id);
|
||||
void markDeviceVerified(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();
|
||||
|
||||
|
@ -414,8 +415,10 @@ private:
|
|||
if constexpr (isStateEvent_<decltype(e)>) {
|
||||
eventsDb.put(txn, e.event_id, json(e).dump());
|
||||
|
||||
if (e.type != EventType::Unsupported) {
|
||||
if (std::is_same_v<
|
||||
std::remove_cv_t<std::remove_reference_t<decltype(e)>>,
|
||||
std::remove_cv_t<
|
||||
std::remove_reference_t<decltype(e)>>,
|
||||
StateEvent<mtx::events::msg::Redacted>>) {
|
||||
if (e.type == EventType::RoomMember)
|
||||
membersdb.del(txn, e.state_key, "");
|
||||
|
@ -430,8 +433,7 @@ private:
|
|||
{"id", e.event_id},
|
||||
})
|
||||
.dump());
|
||||
} else if (e.type != EventType::Unsupported) {
|
||||
if (e.state_key.empty())
|
||||
} else if (e.state_key.empty())
|
||||
statesdb.put(
|
||||
txn, to_string(e.type), json(e).dump());
|
||||
else
|
||||
|
@ -680,7 +682,10 @@ private:
|
|||
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 QString &token);
|
||||
|
|
|
@ -4,11 +4,9 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <QApplication>
|
||||
#include <QImageReader>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QSettings>
|
||||
#include <QShortcut>
|
||||
|
||||
#include <mtx/responses.hpp>
|
||||
|
||||
|
|
|
@ -17,10 +17,8 @@
|
|||
#include <mtx/events/presence.hpp>
|
||||
#include <mtx/secret_storage.hpp>
|
||||
|
||||
#include <QFrame>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMap>
|
||||
#include <QPixmap>
|
||||
#include <QPoint>
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
|
|
@ -53,6 +53,7 @@ MemberList::roleNames() const
|
|||
{Mxid, "mxid"},
|
||||
{DisplayName, "displayName"},
|
||||
{AvatarUrl, "avatarUrl"},
|
||||
{Trustlevel, "trustlevel"},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -69,6 +70,17 @@ MemberList::data(const QModelIndex &index, int role) const
|
|||
return m_memberList[index.row()].first.display_name;
|
||||
case AvatarUrl:
|
||||
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:
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ public:
|
|||
Mxid,
|
||||
DisplayName,
|
||||
AvatarUrl,
|
||||
Trustlevel,
|
||||
};
|
||||
MemberList(const QString &room_id, QObject *parent = nullptr);
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
#include <QByteArray>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include "Logging.h"
|
||||
|
@ -24,12 +26,24 @@ MxcImageProvider::requestImageResponse(const QString &id, const QSize &requested
|
|||
{
|
||||
auto id_ = id;
|
||||
bool crop = true;
|
||||
if (id.endsWith("?scale")) {
|
||||
double radius = 0;
|
||||
|
||||
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;
|
||||
id_.remove("?scale");
|
||||
} 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);
|
||||
return response;
|
||||
}
|
||||
|
@ -53,14 +67,35 @@ MxcImageResponse::run()
|
|||
}
|
||||
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
|
||||
MxcImageProvider::download(const QString &id,
|
||||
const QSize &requestedSize,
|
||||
std::function<void(QString, QSize, QImage, QString)> then,
|
||||
bool crop)
|
||||
bool crop,
|
||||
double radius)
|
||||
{
|
||||
std::optional<mtx::crypto::EncryptedFile> encryptionInfo;
|
||||
auto temp = infos.find("mxc://" + id);
|
||||
|
@ -69,12 +104,13 @@ MxcImageProvider::download(const QString &id,
|
|||
|
||||
if (requestedSize.isValid() && !encryptionInfo) {
|
||||
QString fileName =
|
||||
QString("%1_%2x%3_%4")
|
||||
QString("%1_%2x%3_%4_radius%5")
|
||||
.arg(QString::fromUtf8(id.toUtf8().toBase64(QByteArray::Base64UrlEncoding |
|
||||
QByteArray::OmitTrailingEquals)))
|
||||
.arg(requestedSize.width())
|
||||
.arg(requestedSize.height())
|
||||
.arg(crop ? "crop" : "scale");
|
||||
.arg(crop ? "crop" : "scale")
|
||||
.arg(radius);
|
||||
QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
|
||||
"/media_cache",
|
||||
fileName);
|
||||
|
@ -86,6 +122,10 @@ MxcImageProvider::download(const QString &id,
|
|||
image = image.scaled(
|
||||
requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
if (radius != 0) {
|
||||
image = clipRadius(std::move(image), radius);
|
||||
}
|
||||
|
||||
if (!image.isNull()) {
|
||||
then(id, requestedSize, image, fileInfo.absoluteFilePath());
|
||||
return;
|
||||
|
@ -100,7 +140,7 @@ MxcImageProvider::download(const QString &id,
|
|||
opts.method = crop ? "crop" : "scale";
|
||||
http::client()->get_thumbnail(
|
||||
opts,
|
||||
[fileInfo, requestedSize, then, id](const std::string &res,
|
||||
[fileInfo, requestedSize, radius, then, id](const std::string &res,
|
||||
mtx::http::RequestErr err) {
|
||||
if (err || res.empty()) {
|
||||
then(id, QSize(), {}, "");
|
||||
|
@ -113,6 +153,10 @@ MxcImageProvider::download(const QString &id,
|
|||
if (!image.isNull()) {
|
||||
image = image.scaled(
|
||||
requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
if (radius != 0) {
|
||||
image = clipRadius(std::move(image), radius);
|
||||
}
|
||||
}
|
||||
image.setText("mxc url", "mxc://" + id);
|
||||
if (image.save(fileInfo.absoluteFilePath(), "png"))
|
||||
|
@ -126,8 +170,12 @@ MxcImageProvider::download(const QString &id,
|
|||
});
|
||||
} else {
|
||||
try {
|
||||
QString fileName = QString::fromUtf8(id.toUtf8().toBase64(
|
||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals));
|
||||
QString fileName =
|
||||
QString("%1_radius%2")
|
||||
.arg(QString::fromUtf8(id.toUtf8().toBase64(
|
||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)))
|
||||
.arg(radius);
|
||||
|
||||
QFileInfo fileInfo(
|
||||
QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
|
||||
"/media_cache",
|
||||
|
@ -148,6 +196,11 @@ MxcImageProvider::download(const QString &id,
|
|||
QImage image = utils::readImage(data);
|
||||
image.setText("mxc url", "mxc://" + id);
|
||||
if (!image.isNull()) {
|
||||
if (radius != 0) {
|
||||
image =
|
||||
clipRadius(std::move(image), radius);
|
||||
}
|
||||
|
||||
then(id,
|
||||
requestedSize,
|
||||
image,
|
||||
|
@ -158,6 +211,11 @@ MxcImageProvider::download(const QString &id,
|
|||
QImage image =
|
||||
utils::readImageFromFile(fileInfo.absoluteFilePath());
|
||||
if (!image.isNull()) {
|
||||
if (radius != 0) {
|
||||
image =
|
||||
clipRadius(std::move(image), radius);
|
||||
}
|
||||
|
||||
then(id,
|
||||
requestedSize,
|
||||
image,
|
||||
|
@ -169,7 +227,7 @@ MxcImageProvider::download(const QString &id,
|
|||
|
||||
http::client()->download(
|
||||
"mxc://" + id.toStdString(),
|
||||
[fileInfo, requestedSize, then, id, encryptionInfo](
|
||||
[fileInfo, requestedSize, then, id, radius, encryptionInfo](
|
||||
const std::string &res,
|
||||
const std::string &,
|
||||
const std::string &originalFilename,
|
||||
|
@ -195,6 +253,10 @@ MxcImageProvider::download(const QString &id,
|
|||
auto data =
|
||||
QByteArray(tempData.data(), (int)tempData.size());
|
||||
QImage image = utils::readImage(data);
|
||||
if (radius != 0) {
|
||||
image = clipRadius(std::move(image), radius);
|
||||
}
|
||||
|
||||
image.setText("original filename",
|
||||
QString::fromStdString(originalFilename));
|
||||
image.setText("mxc url", "mxc://" + id);
|
||||
|
@ -205,6 +267,10 @@ MxcImageProvider::download(const QString &id,
|
|||
|
||||
QImage image =
|
||||
utils::readImageFromFile(fileInfo.absoluteFilePath());
|
||||
if (radius != 0) {
|
||||
image = clipRadius(std::move(image), radius);
|
||||
}
|
||||
|
||||
image.setText("original filename",
|
||||
QString::fromStdString(originalFilename));
|
||||
image.setText("mxc url", "mxc://" + id);
|
||||
|
|
|
@ -19,10 +19,11 @@ class MxcImageResponse
|
|||
, public QRunnable
|
||||
{
|
||||
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_requestedSize(requestedSize)
|
||||
, m_crop(crop)
|
||||
, m_radius(radius)
|
||||
{
|
||||
setAutoDelete(false);
|
||||
}
|
||||
|
@ -39,6 +40,7 @@ public:
|
|||
QSize m_requestedSize;
|
||||
QImage m_image;
|
||||
bool m_crop;
|
||||
double m_radius;
|
||||
};
|
||||
|
||||
class MxcImageProvider
|
||||
|
@ -54,7 +56,8 @@ public slots:
|
|||
static void download(const QString &id,
|
||||
const QSize &requestedSize,
|
||||
std::function<void(QString, QSize, QImage, QString)> then,
|
||||
bool crop = true);
|
||||
bool crop = true,
|
||||
double radius = 0);
|
||||
|
||||
private:
|
||||
QThreadPool pool;
|
||||
|
|
23
src/Olm.cpp
23
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,
|
||||
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,
|
||||
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);
|
||||
|
||||
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);
|
||||
if (!session || force_new_session) {
|
||||
static QMap<QPair<std::string, std::string>, qint64> rateLimit;
|
||||
auto currentTime = QDateTime::currentSecsSinceEpoch();
|
||||
if (rateLimit.value(QPair(user, device)) + 60 * 60 * 10 <
|
||||
currentTime) {
|
||||
|
@ -1318,6 +1321,7 @@ send_encrypted_to_device_messages(const std::map<std::string, std::vector<std::s
|
|||
};
|
||||
};
|
||||
|
||||
if (!claims.one_time_keys.empty())
|
||||
http::client()->claim_keys(claims, BindPks(pks));
|
||||
|
||||
if (!keysToQuery.empty()) {
|
||||
|
@ -1395,16 +1399,33 @@ send_encrypted_to_device_messages(const std::map<std::string, std::vector<std::s
|
|||
continue;
|
||||
}
|
||||
|
||||
auto currentTime = QDateTime::currentSecsSinceEpoch();
|
||||
if (rateLimit.value(QPair(user.first, device_id.get())) +
|
||||
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(" curve25519 {}", pks.curve25519);
|
||||
nhlog::net()->info(" ed25519 {}", pks.ed25519);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QMetaType>
|
||||
#include <QPainter>
|
||||
|
@ -481,6 +482,23 @@ RegisterPage::doUIA(const mtx::user_interactive::Unauthorized &unauthorized)
|
|||
doRegistrationWithAuth(
|
||||
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 {
|
||||
// use fallback
|
||||
auto dialog = new dialogs::FallbackAuth(
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
#include <QResizeEvent>
|
||||
#include <QScrollArea>
|
||||
#include <QScroller>
|
||||
#include <QSettings>
|
||||
#include <QSpinBox>
|
||||
#include <QStandardPaths>
|
||||
#include <QString>
|
||||
|
@ -63,7 +62,6 @@ UserSettings::initialize(std::optional<QString> profile)
|
|||
void
|
||||
UserSettings::load(std::optional<QString> profile)
|
||||
{
|
||||
QSettings settings;
|
||||
tray_ = settings.value("user/window/tray", false).toBool();
|
||||
startInTray_ = settings.value("user/window/start_in_tray", false).toBool();
|
||||
|
||||
|
@ -601,7 +599,6 @@ UserSettings::applyTheme()
|
|||
void
|
||||
UserSettings::save()
|
||||
{
|
||||
QSettings settings;
|
||||
settings.beginGroup("user");
|
||||
|
||||
settings.beginGroup("window");
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <QFontDatabase>
|
||||
#include <QFrame>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QSettings>
|
||||
#include <QSharedPointer>
|
||||
#include <QWidget>
|
||||
|
||||
|
@ -107,6 +108,8 @@ public:
|
|||
static QSharedPointer<UserSettings> instance();
|
||||
static void initialize(std::optional<QString> profile);
|
||||
|
||||
QSettings *qsettings() { return &settings; }
|
||||
|
||||
enum class Presence
|
||||
{
|
||||
AutomaticPresence,
|
||||
|
@ -316,6 +319,8 @@ private:
|
|||
QString homeserver_;
|
||||
QStringList hiddenTags_;
|
||||
|
||||
QSettings settings;
|
||||
|
||||
static QSharedPointer<UserSettings> instance_;
|
||||
};
|
||||
|
||||
|
|
|
@ -28,8 +28,10 @@ ImageOverlay::ImageOverlay(QPixmap image, QWidget *parent)
|
|||
setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <QDialog>
|
||||
#include <QMouseEvent>
|
||||
#include <QPixmap>
|
||||
#include <QShortcut>
|
||||
|
||||
namespace dialogs {
|
||||
|
||||
|
@ -32,5 +33,6 @@ private:
|
|||
QRect content_;
|
||||
QRect close_button_;
|
||||
QRect save_button_;
|
||||
QShortcut *close_shortcut_;
|
||||
};
|
||||
} // dialogs
|
||||
|
|
|
@ -310,7 +310,7 @@ qml_mtx_events::fromRoomEventType(qml_mtx_events::EventType t)
|
|||
return mtx::events::EventType::RoomMessage;
|
||||
//! m.image_pack, currently im.ponies.room_emotes
|
||||
case qml_mtx_events::ImagePackInRoom:
|
||||
return mtx::events::EventType::ImagePackRooms;
|
||||
return mtx::events::EventType::ImagePackInRoom;
|
||||
//! m.image_pack, currently im.ponies.user_emotes
|
||||
case qml_mtx_events::ImagePackInAccountData:
|
||||
return mtx::events::EventType::ImagePackInAccountData;
|
||||
|
@ -418,6 +418,14 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
|
|||
&events,
|
||||
&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);
|
||||
}
|
||||
|
||||
|
@ -1993,6 +2001,15 @@ TimelineModel::roomTopic() const
|
|||
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
|
||||
TimelineModel::roomMemberCount() const
|
||||
{
|
||||
|
|
|
@ -175,6 +175,7 @@ class TimelineModel : public QAbstractListModel
|
|||
Q_PROPERTY(int roomMemberCount READ roomMemberCount NOTIFY roomMemberCountChanged)
|
||||
Q_PROPERTY(bool isEncrypted READ isEncrypted NOTIFY encryptionChanged)
|
||||
Q_PROPERTY(bool isSpace READ isSpace CONSTANT)
|
||||
Q_PROPERTY(int trustlevel READ trustlevel NOTIFY trustlevelChanged)
|
||||
Q_PROPERTY(InputBar *input READ input CONSTANT)
|
||||
Q_PROPERTY(Permissions *permissions READ permissions NOTIFY permissionsChanged)
|
||||
|
||||
|
@ -287,6 +288,7 @@ public:
|
|||
DescInfo lastMessage() const { return lastMessage_; }
|
||||
bool isSpace() const { return isSpace_; }
|
||||
bool isEncrypted() const { return isEncrypted_; }
|
||||
crypto::Trust trustlevel() const;
|
||||
int roomMemberCount() const;
|
||||
|
||||
public slots:
|
||||
|
@ -372,6 +374,7 @@ signals:
|
|||
void updateFlowEventId(std::string event_id);
|
||||
|
||||
void encryptionChanged();
|
||||
void trustlevelChanged();
|
||||
void roomNameChanged();
|
||||
void plainRoomNameChanged();
|
||||
void roomTopicChanged();
|
||||
|
|
|
@ -375,10 +375,12 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
|
|||
}
|
||||
|
||||
void
|
||||
TimelineViewManager::openRoomMembers(QString room_id)
|
||||
TimelineViewManager::openRoomMembers(TimelineModel *room)
|
||||
{
|
||||
MemberList *memberList = new MemberList(room_id, this);
|
||||
emit openRoomMembersDialog(memberList);
|
||||
if (!room)
|
||||
return;
|
||||
MemberList *memberList = new MemberList(room->roomId(), this);
|
||||
emit openRoomMembersDialog(memberList, room);
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -66,7 +66,7 @@ public:
|
|||
Q_INVOKABLE QString userPresence(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 openInviteUsers(QString roomId);
|
||||
Q_INVOKABLE void openGlobalUserProfile(QString userId);
|
||||
|
@ -92,7 +92,7 @@ signals:
|
|||
void focusChanged();
|
||||
void focusInput();
|
||||
void openImageOverlayInternalCb(QString eventId, QImage img);
|
||||
void openRoomMembersDialog(MemberList *members);
|
||||
void openRoomMembersDialog(MemberList *members, TimelineModel *room);
|
||||
void openRoomSettingsDialog(RoomSettings *settings);
|
||||
void openInviteUsersDialog(InviteesModel *invitees);
|
||||
void openProfile(UserProfile *profile);
|
||||
|
|
Loading…
Reference in a new issue