mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-25 12:38:48 +03:00
Merge remote-tracking branch 'nheko-im/master' into video_player_enhancements
This commit is contained in:
commit
a4754e79d2
26 changed files with 777 additions and 171 deletions
|
@ -355,6 +355,7 @@ set(SRC_FILES
|
||||||
src/Olm.cpp
|
src/Olm.cpp
|
||||||
src/RegisterPage.cpp
|
src/RegisterPage.cpp
|
||||||
src/SSOHandler.cpp
|
src/SSOHandler.cpp
|
||||||
|
src/ImagePackModel.cpp
|
||||||
src/TrayIcon.cpp
|
src/TrayIcon.cpp
|
||||||
src/UserSettingsPage.cpp
|
src/UserSettingsPage.cpp
|
||||||
src/UsersModel.cpp
|
src/UsersModel.cpp
|
||||||
|
@ -559,6 +560,7 @@ qt5_wrap_cpp(MOC_HEADERS
|
||||||
src/MxcImageProvider.h
|
src/MxcImageProvider.h
|
||||||
src/RegisterPage.h
|
src/RegisterPage.h
|
||||||
src/SSOHandler.h
|
src/SSOHandler.h
|
||||||
|
src/ImagePackModel.h
|
||||||
src/TrayIcon.h
|
src/TrayIcon.h
|
||||||
src/UserSettingsPage.h
|
src/UserSettingsPage.h
|
||||||
src/UsersModel.h
|
src/UsersModel.h
|
||||||
|
|
1
resources/icons/ui/sticky-note-solid.svg
Normal file
1
resources/icons/ui/sticky-note-solid.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="sticky-note" class="svg-inline--fa fa-sticky-note fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M312 320h136V56c0-13.3-10.7-24-24-24H24C10.7 32 0 42.7 0 56v400c0 13.3 10.7 24 24 24h264V344c0-13.2 10.8-24 24-24zm129 55l-98 98c-4.5 4.5-10.6 7-17 7h-6V352h128v6.1c0 6.3-2.5 12.4-7 16.9z"></path></svg>
|
After Width: | Height: | Size: 429 B |
|
@ -8,6 +8,7 @@ import im.nheko 1.0
|
||||||
|
|
||||||
TextEdit {
|
TextEdit {
|
||||||
id: r
|
id: r
|
||||||
|
|
||||||
textFormat: TextEdit.RichText
|
textFormat: TextEdit.RichText
|
||||||
readOnly: true
|
readOnly: true
|
||||||
focus: false
|
focus: false
|
||||||
|
@ -19,14 +20,13 @@ TextEdit {
|
||||||
onLinkActivated: Nheko.openLink(link)
|
onLinkActivated: Nheko.openLink(link)
|
||||||
ToolTip.visible: hoveredLink
|
ToolTip.visible: hoveredLink
|
||||||
ToolTip.text: hoveredLink
|
ToolTip.text: hoveredLink
|
||||||
|
Component.onCompleted: {
|
||||||
|
TimelineManager.fixImageRendering(r.textDocument, r);
|
||||||
|
}
|
||||||
|
|
||||||
CursorShape {
|
CursorShape {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
cursorShape: hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
TimelineManager.fixImageRendering(r.textDocument, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import "./emoji"
|
||||||
import "./voip"
|
import "./voip"
|
||||||
import QtQuick 2.12
|
import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.3
|
import QtQuick.Controls 2.3
|
||||||
|
@ -87,7 +88,7 @@ Rectangle {
|
||||||
Layout.alignment: Qt.AlignBottom // | Qt.AlignHCenter
|
Layout.alignment: Qt.AlignBottom // | Qt.AlignHCenter
|
||||||
Layout.maximumHeight: Window.height / 4
|
Layout.maximumHeight: Window.height / 4
|
||||||
Layout.minimumHeight: Settings.fontSize
|
Layout.minimumHeight: Settings.fontSize
|
||||||
implicitWidth: inputBar.width - 4 * (22 + 16) - 24
|
implicitWidth: inputBar.width - 5 * (22 + 16) - 24
|
||||||
|
|
||||||
TextArea {
|
TextArea {
|
||||||
id: messageInput
|
id: messageInput
|
||||||
|
@ -319,6 +320,30 @@ Rectangle {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImageButton {
|
||||||
|
id: stickerButton
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
|
||||||
|
Layout.margins: 8
|
||||||
|
hoverEnabled: true
|
||||||
|
width: 22
|
||||||
|
height: 22
|
||||||
|
image: ":/icons/icons/ui/sticky-note-solid.svg"
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: qsTr("Stickers")
|
||||||
|
onClicked: stickerPopup.visible ? stickerPopup.close() : stickerPopup.show(stickerButton, room.roomId(), function(row) {
|
||||||
|
room.input.sticker(stickerPopup.model.sourceModel, row);
|
||||||
|
TimelineManager.focusMessageInput();
|
||||||
|
})
|
||||||
|
|
||||||
|
StickerPicker {
|
||||||
|
id: stickerPopup
|
||||||
|
|
||||||
|
colors: Nheko.colors
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
id: emojiButton
|
id: emojiButton
|
||||||
|
|
||||||
|
@ -330,7 +355,7 @@ Rectangle {
|
||||||
image: ":/icons/icons/ui/smile.png"
|
image: ":/icons/icons/ui/smile.png"
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
ToolTip.text: qsTr("Emoji")
|
ToolTip.text: qsTr("Emoji")
|
||||||
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(emojiButton, function(emoji) {
|
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(function(emoji) {
|
||||||
messageInput.insert(messageInput.cursorPosition, emoji);
|
messageInput.insert(messageInput.cursorPosition, emoji);
|
||||||
TimelineManager.focusMessageInput();
|
TimelineManager.focusMessageInput();
|
||||||
})
|
})
|
||||||
|
|
|
@ -92,16 +92,20 @@ ScrollView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EmojiButton {
|
ImageButton {
|
||||||
id: reactButton
|
id: reactButton
|
||||||
|
|
||||||
visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false
|
visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false
|
||||||
width: 16
|
width: 16
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
image: ":/icons/icons/ui/smile.png"
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
ToolTip.text: qsTr("React")
|
ToolTip.text: qsTr("React")
|
||||||
emojiPicker: emojiPopup
|
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, function(emoji) {
|
||||||
event_id: row.model ? row.model.eventId : ""
|
var event_id = row.model ? row.model.eventId : "";
|
||||||
|
room.input.reaction(event_id, emoji);
|
||||||
|
TimelineManager.focusMessageInput();
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageButton {
|
ImageButton {
|
||||||
|
|
|
@ -86,29 +86,29 @@ Item {
|
||||||
// fancy reply, if this is a reply
|
// fancy reply, if this is a reply
|
||||||
Reply {
|
Reply {
|
||||||
function fromModel(role) {
|
function fromModel(role) {
|
||||||
return replyTo != "" ? room.dataById(replyTo, role) : null;
|
return replyTo != "" ? room.dataById(replyTo, role, r.eventId) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
visible: replyTo
|
visible: replyTo
|
||||||
userColor: TimelineManager.userColor(userId, Nheko.colors.base)
|
userColor: replyTo, TimelineManager.userColor(userId, Nheko.colors.base)
|
||||||
blurhash: fromModel(Room.Blurhash) ?? ""
|
blurhash: replyTo, fromModel(Room.Blurhash) ?? ""
|
||||||
body: fromModel(Room.Body) ?? ""
|
body: replyTo, fromModel(Room.Body) ?? ""
|
||||||
formattedBody: fromModel(Room.FormattedBody) ?? ""
|
formattedBody: replyTo, fromModel(Room.FormattedBody) ?? ""
|
||||||
eventId: fromModel(Room.EventId) ?? ""
|
eventId: fromModel(Room.EventId) ?? ""
|
||||||
filename: fromModel(Room.Filename) ?? ""
|
filename: replyTo, fromModel(Room.Filename) ?? ""
|
||||||
filesize: fromModel(Room.Filesize) ?? ""
|
filesize: replyTo, fromModel(Room.Filesize) ?? ""
|
||||||
proportionalHeight: fromModel(Room.ProportionalHeight) ?? 1
|
proportionalHeight: replyTo, fromModel(Room.ProportionalHeight) ?? 1
|
||||||
type: fromModel(Room.Type) ?? MtxEvent.UnknownMessage
|
type: replyTo, fromModel(Room.Type) ?? MtxEvent.UnknownMessage
|
||||||
typeString: fromModel(Room.TypeString) ?? ""
|
typeString: replyTo, fromModel(Room.TypeString) ?? ""
|
||||||
url: fromModel(Room.Url) ?? ""
|
url: replyTo, fromModel(Room.Url) ?? ""
|
||||||
originalWidth: fromModel(Room.OriginalWidth) ?? 0
|
originalWidth: replyTo, fromModel(Room.OriginalWidth) ?? 0
|
||||||
isOnlyEmoji: fromModel(Room.IsOnlyEmoji) ?? false
|
isOnlyEmoji: replyTo, fromModel(Room.IsOnlyEmoji) ?? false
|
||||||
userId: fromModel(Room.UserId) ?? ""
|
userId: replyTo, fromModel(Room.UserId) ?? ""
|
||||||
userName: fromModel(Room.UserName) ?? ""
|
userName: replyTo, fromModel(Room.UserName) ?? ""
|
||||||
thumbnailUrl: fromModel(Room.ThumbnailUrl) ?? ""
|
thumbnailUrl: replyTo, fromModel(Room.ThumbnailUrl) ?? ""
|
||||||
roomTopic: fromModel(Room.RoomTopic) ?? ""
|
roomTopic: replyTo, fromModel(Room.RoomTopic) ?? ""
|
||||||
roomName: fromModel(Room.RoomName) ?? ""
|
roomName: replyTo, fromModel(Room.RoomName) ?? ""
|
||||||
callType: fromModel(Room.CallType) ?? ""
|
callType: replyTo, fromModel(Room.CallType) ?? ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// actual message content
|
// actual message content
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
import "../"
|
|
||||||
import QtQuick 2.10
|
|
||||||
import QtQuick.Controls 2.1
|
|
||||||
import im.nheko 1.0
|
|
||||||
import im.nheko.EmojiModel 1.0
|
|
||||||
|
|
||||||
ImageButton {
|
|
||||||
id: emojiButton
|
|
||||||
|
|
||||||
property var colors: currentActivePalette
|
|
||||||
property var emojiPicker
|
|
||||||
property string event_id
|
|
||||||
|
|
||||||
image: ":/icons/icons/ui/smile.png"
|
|
||||||
onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.show(emojiButton, function(emoji) {
|
|
||||||
room.input.reaction(event_id, emoji);
|
|
||||||
TimelineManager.focusMessageInput();
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -130,6 +130,7 @@ Menu {
|
||||||
boundsBehavior: Flickable.StopAtBounds
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
clip: true
|
clip: true
|
||||||
currentIndex: -1 // prevent sorting from stealing focus
|
currentIndex: -1 // prevent sorting from stealing focus
|
||||||
|
cacheBuffer: 500
|
||||||
|
|
||||||
// Individual emoji
|
// Individual emoji
|
||||||
delegate: AbstractButton {
|
delegate: AbstractButton {
|
||||||
|
|
181
resources/qml/emoji/StickerPicker.qml
Normal file
181
resources/qml/emoji/StickerPicker.qml
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import "../"
|
||||||
|
import QtGraphicalEffects 1.0
|
||||||
|
import QtQuick 2.9
|
||||||
|
import QtQuick.Controls 2.3
|
||||||
|
import QtQuick.Layouts 1.3
|
||||||
|
import im.nheko 1.0
|
||||||
|
import im.nheko.EmojiModel 1.0
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
id: stickerPopup
|
||||||
|
|
||||||
|
property var callback
|
||||||
|
property var colors
|
||||||
|
property string roomid
|
||||||
|
property alias model: gridView.model
|
||||||
|
property var textArea
|
||||||
|
property real highlightHue: Nheko.colors.highlight.hslHue
|
||||||
|
property real highlightSat: Nheko.colors.highlight.hslSaturation
|
||||||
|
property real highlightLight: Nheko.colors.highlight.hslLightness
|
||||||
|
readonly property int stickerDim: 128
|
||||||
|
readonly property int stickerDimPad: 128 + Nheko.paddingSmall
|
||||||
|
readonly property int stickersPerRow: 3
|
||||||
|
|
||||||
|
function show(showAt, roomid_, callback) {
|
||||||
|
console.debug("Showing sticker picker");
|
||||||
|
roomid = roomid_;
|
||||||
|
stickerPopup.callback = callback;
|
||||||
|
popup(showAt ? showAt : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
margins: 0
|
||||||
|
bottomPadding: 1
|
||||||
|
leftPadding: 1
|
||||||
|
rightPadding: 1
|
||||||
|
modal: true
|
||||||
|
focus: true
|
||||||
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||||
|
width: stickersPerRow * stickerDimPad + 20
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
color: Nheko.colors.window
|
||||||
|
height: columnView.implicitHeight + 4
|
||||||
|
width: stickersPerRow * stickerDimPad + 20
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: columnView
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
anchors.leftMargin: 3
|
||||||
|
anchors.rightMargin: 3
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.topMargin: 2
|
||||||
|
|
||||||
|
// Search field
|
||||||
|
TextField {
|
||||||
|
id: emojiSearch
|
||||||
|
|
||||||
|
Layout.topMargin: 3
|
||||||
|
Layout.preferredWidth: stickersPerRow * stickerDimPad + 20 - 6
|
||||||
|
palette: Nheko.colors
|
||||||
|
background: null
|
||||||
|
placeholderTextColor: Nheko.colors.buttonText
|
||||||
|
color: Nheko.colors.text
|
||||||
|
placeholderText: qsTr("Search")
|
||||||
|
selectByMouse: true
|
||||||
|
rightPadding: clearSearch.width
|
||||||
|
onTextChanged: searchTimer.restart()
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible)
|
||||||
|
forceActiveFocus();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: searchTimer
|
||||||
|
|
||||||
|
interval: 350 // tweak as needed?
|
||||||
|
onTriggered: stickerPopup.model.searchString = emojiSearch.text
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
id: clearSearch
|
||||||
|
|
||||||
|
visible: emojiSearch.text !== ''
|
||||||
|
icon.source: "image://colorimage/:/icons/icons/ui/round-remove-button.png?" + (clearSearch.hovered ? Nheko.colors.highlight : Nheko.colors.buttonText)
|
||||||
|
focusPolicy: Qt.NoFocus
|
||||||
|
onClicked: emojiSearch.clear()
|
||||||
|
hoverEnabled: true
|
||||||
|
background: null
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
right: parent.right
|
||||||
|
}
|
||||||
|
// clear the default hover effects.
|
||||||
|
|
||||||
|
Image {
|
||||||
|
height: parent.height - 2 * Nheko.paddingSmall
|
||||||
|
width: height
|
||||||
|
source: "image://colorimage/:/icons/icons/ui/round-remove-button.png?" + (clearSearch.hovered ? Nheko.colors.highlight : Nheko.colors.buttonText)
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
right: parent.right
|
||||||
|
margins: Nheko.paddingSmall
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// emoji grid
|
||||||
|
GridView {
|
||||||
|
id: gridView
|
||||||
|
|
||||||
|
model: roomid ? TimelineManager.completerFor("stickers", roomid) : null
|
||||||
|
|
||||||
|
Layout.preferredHeight: cellHeight * 3.5
|
||||||
|
Layout.preferredWidth: stickersPerRow * stickerDimPad + 20
|
||||||
|
Layout.leftMargin: 4
|
||||||
|
cellWidth: stickerDimPad
|
||||||
|
cellHeight: stickerDimPad
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
clip: true
|
||||||
|
currentIndex: -1 // prevent sorting from stealing focus
|
||||||
|
cacheBuffer: 500
|
||||||
|
|
||||||
|
ScrollHelper {
|
||||||
|
flickable: parent
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: !Settings.mobileMode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Individual emoji
|
||||||
|
delegate: AbstractButton {
|
||||||
|
width: stickerDim
|
||||||
|
height: stickerDim
|
||||||
|
hoverEnabled: true
|
||||||
|
ToolTip.text: ":" + model.shortcode + ": - " + model.body
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
// TODO: maybe add favorites at some point?
|
||||||
|
onClicked: {
|
||||||
|
console.debug("Picked " + model.shortcode);
|
||||||
|
stickerPopup.close();
|
||||||
|
callback(model.originalRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Image {
|
||||||
|
height: stickerDim
|
||||||
|
width: stickerDim
|
||||||
|
source: model.url.replace("mxc://", "image://MxcImage/")
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
color: hovered ? Nheko.colors.highlight : 'transparent'
|
||||||
|
radius: 5
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {
|
||||||
|
id: emojiScroll
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -27,6 +27,7 @@
|
||||||
<file>icons/ui/search@2x.png</file>
|
<file>icons/ui/search@2x.png</file>
|
||||||
<file>icons/ui/settings.png</file>
|
<file>icons/ui/settings.png</file>
|
||||||
<file>icons/ui/settings@2x.png</file>
|
<file>icons/ui/settings@2x.png</file>
|
||||||
|
<file>icons/ui/sticky-note-solid.svg</file>
|
||||||
<file>icons/ui/smile.png</file>
|
<file>icons/ui/smile.png</file>
|
||||||
<file>icons/ui/smile@2x.png</file>
|
<file>icons/ui/smile@2x.png</file>
|
||||||
<file>icons/ui/speech-bubbles-comment-option.png</file>
|
<file>icons/ui/speech-bubbles-comment-option.png</file>
|
||||||
|
@ -151,8 +152,8 @@
|
||||||
<file>qml/ForwardCompleter.qml</file>
|
<file>qml/ForwardCompleter.qml</file>
|
||||||
<file>qml/TypingIndicator.qml</file>
|
<file>qml/TypingIndicator.qml</file>
|
||||||
<file>qml/RoomSettings.qml</file>
|
<file>qml/RoomSettings.qml</file>
|
||||||
<file>qml/emoji/EmojiButton.qml</file>
|
|
||||||
<file>qml/emoji/EmojiPicker.qml</file>
|
<file>qml/emoji/EmojiPicker.qml</file>
|
||||||
|
<file>qml/emoji/StickerPicker.qml</file>
|
||||||
<file>qml/UserProfile.qml</file>
|
<file>qml/UserProfile.qml</file>
|
||||||
<file>qml/delegates/MessageDelegate.qml</file>
|
<file>qml/delegates/MessageDelegate.qml</file>
|
||||||
<file>qml/delegates/TextMessage.qml</file>
|
<file>qml/delegates/TextMessage.qml</file>
|
||||||
|
|
193
src/Cache.cpp
193
src/Cache.cpp
|
@ -78,6 +78,8 @@ constexpr auto ENCRYPTED_ROOMS_DB("encrypted_rooms");
|
||||||
constexpr auto INBOUND_MEGOLM_SESSIONS_DB("inbound_megolm_sessions");
|
constexpr auto INBOUND_MEGOLM_SESSIONS_DB("inbound_megolm_sessions");
|
||||||
//! MegolmSessionIndex -> pickled OlmOutboundGroupSession
|
//! MegolmSessionIndex -> pickled OlmOutboundGroupSession
|
||||||
constexpr auto OUTBOUND_MEGOLM_SESSIONS_DB("outbound_megolm_sessions");
|
constexpr auto OUTBOUND_MEGOLM_SESSIONS_DB("outbound_megolm_sessions");
|
||||||
|
//! MegolmSessionIndex -> session data about which devices have access to this
|
||||||
|
constexpr auto MEGOLM_SESSIONS_DATA_DB("megolm_sessions_data_db");
|
||||||
|
|
||||||
using CachedReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
|
using CachedReceipts = std::multimap<uint64_t, std::string, std::greater<uint64_t>>;
|
||||||
using Receipts = std::map<std::string, std::map<std::string, uint64_t>>;
|
using Receipts = std::map<std::string, std::map<std::string, uint64_t>>;
|
||||||
|
@ -284,6 +286,7 @@ Cache::setup()
|
||||||
// Session management
|
// Session management
|
||||||
inboundMegolmSessionDb_ = lmdb::dbi::open(txn, INBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE);
|
inboundMegolmSessionDb_ = lmdb::dbi::open(txn, INBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE);
|
||||||
outboundMegolmSessionDb_ = lmdb::dbi::open(txn, OUTBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE);
|
outboundMegolmSessionDb_ = lmdb::dbi::open(txn, OUTBOUND_MEGOLM_SESSIONS_DB, MDB_CREATE);
|
||||||
|
megolmSessionDataDb_ = lmdb::dbi::open(txn, MEGOLM_SESSIONS_DATA_DB, MDB_CREATE);
|
||||||
|
|
||||||
txn.commit();
|
txn.commit();
|
||||||
|
|
||||||
|
@ -387,9 +390,14 @@ Cache::importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys)
|
||||||
index.session_id = s.session_id;
|
index.session_id = s.session_id;
|
||||||
index.sender_key = s.sender_key;
|
index.sender_key = s.sender_key;
|
||||||
|
|
||||||
|
GroupSessionData data{};
|
||||||
|
data.forwarding_curve25519_key_chain = s.forwarding_curve25519_key_chain;
|
||||||
|
if (s.sender_claimed_keys.count("ed25519"))
|
||||||
|
data.sender_claimed_ed25519_key = s.sender_claimed_keys.at("ed25519");
|
||||||
|
|
||||||
auto exported_session = mtx::crypto::import_session(s.session_key);
|
auto exported_session = mtx::crypto::import_session(s.session_key);
|
||||||
|
|
||||||
saveInboundMegolmSession(index, std::move(exported_session));
|
saveInboundMegolmSession(index, std::move(exported_session), data);
|
||||||
ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id);
|
ChatPage::instance()->receivedSessionKey(index.room_id, index.session_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -400,7 +408,8 @@ Cache::importSessionKeys(const mtx::crypto::ExportedSessionKeys &keys)
|
||||||
|
|
||||||
void
|
void
|
||||||
Cache::saveInboundMegolmSession(const MegolmSessionIndex &index,
|
Cache::saveInboundMegolmSession(const MegolmSessionIndex &index,
|
||||||
mtx::crypto::InboundGroupSessionPtr session)
|
mtx::crypto::InboundGroupSessionPtr session,
|
||||||
|
const GroupSessionData &data)
|
||||||
{
|
{
|
||||||
using namespace mtx::crypto;
|
using namespace mtx::crypto;
|
||||||
const auto key = json(index).dump();
|
const auto key = json(index).dump();
|
||||||
|
@ -420,6 +429,7 @@ Cache::saveInboundMegolmSession(const MegolmSessionIndex &index,
|
||||||
}
|
}
|
||||||
|
|
||||||
inboundMegolmSessionDb_.put(txn, key, pickled);
|
inboundMegolmSessionDb_.put(txn, key, pickled);
|
||||||
|
megolmSessionDataDb_.put(txn, key, json(data).dump());
|
||||||
txn.commit();
|
txn.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,7 +474,7 @@ Cache::inboundMegolmSessionExists(const MegolmSessionIndex &index)
|
||||||
|
|
||||||
void
|
void
|
||||||
Cache::updateOutboundMegolmSession(const std::string &room_id,
|
Cache::updateOutboundMegolmSession(const std::string &room_id,
|
||||||
const OutboundGroupSessionData &data_,
|
const GroupSessionData &data_,
|
||||||
mtx::crypto::OutboundGroupSessionPtr &ptr)
|
mtx::crypto::OutboundGroupSessionPtr &ptr)
|
||||||
{
|
{
|
||||||
using namespace mtx::crypto;
|
using namespace mtx::crypto;
|
||||||
|
@ -472,18 +482,20 @@ Cache::updateOutboundMegolmSession(const std::string &room_id,
|
||||||
if (!outboundMegolmSessionExists(room_id))
|
if (!outboundMegolmSessionExists(room_id))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
OutboundGroupSessionData data = data_;
|
GroupSessionData data = data_;
|
||||||
data.message_index = olm_outbound_group_session_message_index(ptr.get());
|
data.message_index = olm_outbound_group_session_message_index(ptr.get());
|
||||||
data.session_id = mtx::crypto::session_id(ptr.get());
|
MegolmSessionIndex index;
|
||||||
data.session_key = mtx::crypto::session_key(ptr.get());
|
index.room_id = room_id;
|
||||||
|
index.sender_key = olm::client()->identity_keys().ed25519;
|
||||||
|
index.session_id = mtx::crypto::session_id(ptr.get());
|
||||||
|
|
||||||
// Save the updated pickled data for the session.
|
// Save the updated pickled data for the session.
|
||||||
json j;
|
json j;
|
||||||
j["data"] = data;
|
|
||||||
j["session"] = pickle<OutboundSessionObject>(ptr.get(), SECRET);
|
j["session"] = pickle<OutboundSessionObject>(ptr.get(), SECRET);
|
||||||
|
|
||||||
auto txn = lmdb::txn::begin(env_);
|
auto txn = lmdb::txn::begin(env_);
|
||||||
outboundMegolmSessionDb_.put(txn, room_id, j.dump());
|
outboundMegolmSessionDb_.put(txn, room_id, j.dump());
|
||||||
|
megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump());
|
||||||
txn.commit();
|
txn.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,24 +510,32 @@ Cache::dropOutboundMegolmSession(const std::string &room_id)
|
||||||
{
|
{
|
||||||
auto txn = lmdb::txn::begin(env_);
|
auto txn = lmdb::txn::begin(env_);
|
||||||
outboundMegolmSessionDb_.del(txn, room_id);
|
outboundMegolmSessionDb_.del(txn, room_id);
|
||||||
|
// don't delete session data, so that we can still share the session.
|
||||||
txn.commit();
|
txn.commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Cache::saveOutboundMegolmSession(const std::string &room_id,
|
Cache::saveOutboundMegolmSession(const std::string &room_id,
|
||||||
const OutboundGroupSessionData &data,
|
const GroupSessionData &data_,
|
||||||
mtx::crypto::OutboundGroupSessionPtr &session)
|
mtx::crypto::OutboundGroupSessionPtr &session)
|
||||||
{
|
{
|
||||||
using namespace mtx::crypto;
|
using namespace mtx::crypto;
|
||||||
const auto pickled = pickle<OutboundSessionObject>(session.get(), SECRET);
|
const auto pickled = pickle<OutboundSessionObject>(session.get(), SECRET);
|
||||||
|
|
||||||
|
GroupSessionData data = data_;
|
||||||
|
data.message_index = olm_outbound_group_session_message_index(session.get());
|
||||||
|
MegolmSessionIndex index;
|
||||||
|
index.room_id = room_id;
|
||||||
|
index.sender_key = olm::client()->identity_keys().ed25519;
|
||||||
|
index.session_id = mtx::crypto::session_id(session.get());
|
||||||
|
|
||||||
json j;
|
json j;
|
||||||
j["data"] = data;
|
|
||||||
j["session"] = pickled;
|
j["session"] = pickled;
|
||||||
|
|
||||||
auto txn = lmdb::txn::begin(env_);
|
auto txn = lmdb::txn::begin(env_);
|
||||||
outboundMegolmSessionDb_.put(txn, room_id, j.dump());
|
outboundMegolmSessionDb_.put(txn, room_id, j.dump());
|
||||||
|
megolmSessionDataDb_.put(txn, json(index).dump(), json(data).dump());
|
||||||
txn.commit();
|
txn.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -544,8 +564,17 @@ Cache::getOutboundMegolmSession(const std::string &room_id)
|
||||||
auto obj = json::parse(value);
|
auto obj = json::parse(value);
|
||||||
|
|
||||||
OutboundGroupSessionDataRef ref{};
|
OutboundGroupSessionDataRef ref{};
|
||||||
ref.data = obj.at("data").get<OutboundGroupSessionData>();
|
|
||||||
ref.session = unpickle<OutboundSessionObject>(obj.at("session"), SECRET);
|
ref.session = unpickle<OutboundSessionObject>(obj.at("session"), SECRET);
|
||||||
|
|
||||||
|
MegolmSessionIndex index;
|
||||||
|
index.room_id = room_id;
|
||||||
|
index.sender_key = olm::client()->identity_keys().ed25519;
|
||||||
|
index.session_id = mtx::crypto::session_id(ref.session.get());
|
||||||
|
|
||||||
|
if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) {
|
||||||
|
ref.data = nlohmann::json::parse(value).get<GroupSessionData>();
|
||||||
|
}
|
||||||
|
|
||||||
return ref;
|
return ref;
|
||||||
} catch (std::exception &e) {
|
} catch (std::exception &e) {
|
||||||
nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what());
|
nhlog::db()->error("Failed to retrieve outbound Megolm Session: {}", e.what());
|
||||||
|
@ -553,6 +582,25 @@ Cache::getOutboundMegolmSession(const std::string &room_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<GroupSessionData>
|
||||||
|
Cache::getMegolmSessionData(const MegolmSessionIndex &index)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
using namespace mtx::crypto;
|
||||||
|
|
||||||
|
auto txn = ro_txn(env_);
|
||||||
|
|
||||||
|
std::string_view value;
|
||||||
|
if (megolmSessionDataDb_.get(txn, json(index).dump(), value)) {
|
||||||
|
return nlohmann::json::parse(value).get<GroupSessionData>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
} catch (std::exception &e) {
|
||||||
|
nhlog::db()->error("Failed to retrieve Megolm Session Data: {}", e.what());
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
//
|
//
|
||||||
// OLM sessions.
|
// OLM sessions.
|
||||||
//
|
//
|
||||||
|
@ -829,6 +877,7 @@ Cache::deleteData()
|
||||||
|
|
||||||
lmdb::dbi_close(env_, inboundMegolmSessionDb_);
|
lmdb::dbi_close(env_, inboundMegolmSessionDb_);
|
||||||
lmdb::dbi_close(env_, outboundMegolmSessionDb_);
|
lmdb::dbi_close(env_, outboundMegolmSessionDb_);
|
||||||
|
lmdb::dbi_close(env_, megolmSessionDataDb_);
|
||||||
|
|
||||||
env_.close();
|
env_.close();
|
||||||
|
|
||||||
|
@ -3333,6 +3382,75 @@ Cache::getChildRoomIds(const std::string &room_id)
|
||||||
return roomids;
|
return roomids;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<ImagePackInfo>
|
||||||
|
Cache::getImagePacks(const std::string &room_id, bool stickers)
|
||||||
|
{
|
||||||
|
auto txn = ro_txn(env_);
|
||||||
|
std::vector<ImagePackInfo> infos;
|
||||||
|
|
||||||
|
auto addPack = [&infos, stickers](const mtx::events::msc2545::ImagePack &pack) {
|
||||||
|
if (!pack.pack || (stickers ? pack.pack->is_sticker() : pack.pack->is_emoji())) {
|
||||||
|
ImagePackInfo info;
|
||||||
|
if (pack.pack)
|
||||||
|
info.packname = pack.pack->display_name;
|
||||||
|
|
||||||
|
for (const auto &img : pack.images) {
|
||||||
|
if (img.second.overrides_usage() &&
|
||||||
|
(stickers ? !img.second.is_sticker() : !img.second.is_emoji()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
info.images.insert(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!info.images.empty())
|
||||||
|
infos.push_back(std::move(info));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// packs from account data
|
||||||
|
if (auto accountpack =
|
||||||
|
getAccountData(txn, mtx::events::EventType::ImagePackInAccountData, "")) {
|
||||||
|
auto tmp =
|
||||||
|
std::get_if<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePack>>(
|
||||||
|
&*accountpack);
|
||||||
|
if (tmp)
|
||||||
|
addPack(tmp->content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// packs from rooms, that were enabled globally
|
||||||
|
if (auto roomPacks = getAccountData(txn, mtx::events::EventType::ImagePackRooms, "")) {
|
||||||
|
auto tmp =
|
||||||
|
std::get_if<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>(
|
||||||
|
&*roomPacks);
|
||||||
|
if (tmp) {
|
||||||
|
for (const auto &[room_id2, state_to_d] : tmp->content.rooms) {
|
||||||
|
// don't add stickers from this room twice
|
||||||
|
if (room_id2 == room_id)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (const auto &[state_id, d] : state_to_d) {
|
||||||
|
(void)d;
|
||||||
|
if (auto pack =
|
||||||
|
getStateEvent<mtx::events::msc2545::ImagePack>(
|
||||||
|
txn, room_id2, state_id))
|
||||||
|
addPack(pack->content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// packs from current room
|
||||||
|
if (auto pack = getStateEvent<mtx::events::msc2545::ImagePack>(txn, room_id)) {
|
||||||
|
addPack(pack->content);
|
||||||
|
}
|
||||||
|
for (const auto &pack :
|
||||||
|
getStateEventsWithType<mtx::events::msc2545::ImagePack>(txn, room_id)) {
|
||||||
|
addPack(pack.content);
|
||||||
|
}
|
||||||
|
|
||||||
|
return infos;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<mtx::events::collections::RoomAccountDataEvents>
|
std::optional<mtx::events::collections::RoomAccountDataEvents>
|
||||||
Cache::getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id)
|
Cache::getAccountData(lmdb::txn &txn, mtx::events::EventType type, const std::string &room_id)
|
||||||
{
|
{
|
||||||
|
@ -3525,6 +3643,7 @@ to_json(json &j, const UserKeyCache &info)
|
||||||
{
|
{
|
||||||
j["device_keys"] = info.device_keys;
|
j["device_keys"] = info.device_keys;
|
||||||
j["seen_device_keys"] = info.seen_device_keys;
|
j["seen_device_keys"] = info.seen_device_keys;
|
||||||
|
j["seen_device_ids"] = info.seen_device_ids;
|
||||||
j["master_keys"] = info.master_keys;
|
j["master_keys"] = info.master_keys;
|
||||||
j["master_key_changed"] = info.master_key_changed;
|
j["master_key_changed"] = info.master_key_changed;
|
||||||
j["user_signing_keys"] = info.user_signing_keys;
|
j["user_signing_keys"] = info.user_signing_keys;
|
||||||
|
@ -3538,6 +3657,7 @@ from_json(const json &j, UserKeyCache &info)
|
||||||
{
|
{
|
||||||
info.device_keys = j.value("device_keys", std::map<std::string, mtx::crypto::DeviceKeys>{});
|
info.device_keys = j.value("device_keys", std::map<std::string, mtx::crypto::DeviceKeys>{});
|
||||||
info.seen_device_keys = j.value("seen_device_keys", std::set<std::string>{});
|
info.seen_device_keys = j.value("seen_device_keys", std::set<std::string>{});
|
||||||
|
info.seen_device_ids = j.value("seen_device_ids", std::set<std::string>{});
|
||||||
info.master_keys = j.value("master_keys", mtx::crypto::CrossSigningKeys{});
|
info.master_keys = j.value("master_keys", mtx::crypto::CrossSigningKeys{});
|
||||||
info.master_key_changed = j.value("master_key_changed", false);
|
info.master_key_changed = j.value("master_key_changed", false);
|
||||||
info.user_signing_keys = j.value("user_signing_keys", mtx::crypto::CrossSigningKeys{});
|
info.user_signing_keys = j.value("user_signing_keys", mtx::crypto::CrossSigningKeys{});
|
||||||
|
@ -3634,6 +3754,15 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query
|
||||||
keyReused = true;
|
keyReused = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (updateToWrite.seen_device_ids.count(
|
||||||
|
device_id)) {
|
||||||
|
nhlog::crypto()->warn(
|
||||||
|
"device_id '{}' reused by ({})",
|
||||||
|
device_id,
|
||||||
|
user);
|
||||||
|
keyReused = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!keyReused && !oldDeviceKeys.count(device_id))
|
if (!keyReused && !oldDeviceKeys.count(device_id))
|
||||||
|
@ -3644,6 +3773,7 @@ Cache::updateUserKeys(const std::string &sync_token, const mtx::responses::Query
|
||||||
(void)key_id;
|
(void)key_id;
|
||||||
updateToWrite.seen_device_keys.insert(key);
|
updateToWrite.seen_device_keys.insert(key);
|
||||||
}
|
}
|
||||||
|
updateToWrite.seen_device_ids.insert(device_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.put(txn, user, json(updateToWrite).dump());
|
db.put(txn, user, json(updateToWrite).dump());
|
||||||
|
@ -4077,17 +4207,15 @@ from_json(const json &j, MemberInfo &info)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
to_json(nlohmann::json &obj, const DeviceAndMasterKeys &msg)
|
to_json(nlohmann::json &obj, const DeviceKeysToMsgIndex &msg)
|
||||||
{
|
{
|
||||||
obj["devices"] = msg.devices;
|
obj["deviceids"] = msg.deviceids;
|
||||||
obj["master_keys"] = msg.master_keys;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
from_json(const nlohmann::json &obj, DeviceAndMasterKeys &msg)
|
from_json(const nlohmann::json &obj, DeviceKeysToMsgIndex &msg)
|
||||||
{
|
{
|
||||||
msg.devices = obj.at("devices").get<decltype(msg.devices)>();
|
msg.deviceids = obj.at("deviceids").get<decltype(msg.deviceids)>();
|
||||||
msg.master_keys = obj.at("master_keys").get<decltype(msg.master_keys)>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -4099,30 +4227,31 @@ to_json(nlohmann::json &obj, const SharedWithUsers &msg)
|
||||||
void
|
void
|
||||||
from_json(const nlohmann::json &obj, SharedWithUsers &msg)
|
from_json(const nlohmann::json &obj, SharedWithUsers &msg)
|
||||||
{
|
{
|
||||||
msg.keys = obj.at("keys").get<std::map<std::string, DeviceAndMasterKeys>>();
|
msg.keys = obj.at("keys").get<std::map<std::string, DeviceKeysToMsgIndex>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
to_json(nlohmann::json &obj, const OutboundGroupSessionData &msg)
|
to_json(nlohmann::json &obj, const GroupSessionData &msg)
|
||||||
{
|
{
|
||||||
obj["session_id"] = msg.session_id;
|
|
||||||
obj["session_key"] = msg.session_key;
|
|
||||||
obj["message_index"] = msg.message_index;
|
obj["message_index"] = msg.message_index;
|
||||||
obj["ts"] = msg.timestamp;
|
obj["ts"] = msg.timestamp;
|
||||||
|
|
||||||
obj["initially"] = msg.initially;
|
obj["sender_claimed_ed25519_key"] = msg.sender_claimed_ed25519_key;
|
||||||
|
obj["forwarding_curve25519_key_chain"] = msg.forwarding_curve25519_key_chain;
|
||||||
|
|
||||||
obj["currently"] = msg.currently;
|
obj["currently"] = msg.currently;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
from_json(const nlohmann::json &obj, OutboundGroupSessionData &msg)
|
from_json(const nlohmann::json &obj, GroupSessionData &msg)
|
||||||
{
|
{
|
||||||
msg.session_id = obj.at("session_id");
|
|
||||||
msg.session_key = obj.at("session_key");
|
|
||||||
msg.message_index = obj.at("message_index");
|
msg.message_index = obj.at("message_index");
|
||||||
msg.timestamp = obj.value("ts", 0ULL);
|
msg.timestamp = obj.value("ts", 0ULL);
|
||||||
|
|
||||||
msg.initially = obj.value("initially", SharedWithUsers{});
|
msg.sender_claimed_ed25519_key = obj.value("sender_claimed_ed25519_key", "");
|
||||||
|
msg.forwarding_curve25519_key_chain =
|
||||||
|
obj.value("forwarding_curve25519_key_chain", std::vector<std::string>{});
|
||||||
|
|
||||||
msg.currently = obj.value("currently", SharedWithUsers{});
|
msg.currently = obj.value("currently", SharedWithUsers{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4522,7 +4651,7 @@ isRoomMember(const std::string &user_id, const std::string &room_id)
|
||||||
//
|
//
|
||||||
void
|
void
|
||||||
saveOutboundMegolmSession(const std::string &room_id,
|
saveOutboundMegolmSession(const std::string &room_id,
|
||||||
const OutboundGroupSessionData &data,
|
const GroupSessionData &data,
|
||||||
mtx::crypto::OutboundGroupSessionPtr &session)
|
mtx::crypto::OutboundGroupSessionPtr &session)
|
||||||
{
|
{
|
||||||
instance_->saveOutboundMegolmSession(room_id, data, session);
|
instance_->saveOutboundMegolmSession(room_id, data, session);
|
||||||
|
@ -4539,7 +4668,7 @@ outboundMegolmSessionExists(const std::string &room_id) noexcept
|
||||||
}
|
}
|
||||||
void
|
void
|
||||||
updateOutboundMegolmSession(const std::string &room_id,
|
updateOutboundMegolmSession(const std::string &room_id,
|
||||||
const OutboundGroupSessionData &data,
|
const GroupSessionData &data,
|
||||||
mtx::crypto::OutboundGroupSessionPtr &session)
|
mtx::crypto::OutboundGroupSessionPtr &session)
|
||||||
{
|
{
|
||||||
instance_->updateOutboundMegolmSession(room_id, data, session);
|
instance_->updateOutboundMegolmSession(room_id, data, session);
|
||||||
|
@ -4566,9 +4695,10 @@ exportSessionKeys()
|
||||||
//
|
//
|
||||||
void
|
void
|
||||||
saveInboundMegolmSession(const MegolmSessionIndex &index,
|
saveInboundMegolmSession(const MegolmSessionIndex &index,
|
||||||
mtx::crypto::InboundGroupSessionPtr session)
|
mtx::crypto::InboundGroupSessionPtr session,
|
||||||
|
const GroupSessionData &data)
|
||||||
{
|
{
|
||||||
instance_->saveInboundMegolmSession(index, std::move(session));
|
instance_->saveInboundMegolmSession(index, std::move(session), data);
|
||||||
}
|
}
|
||||||
mtx::crypto::InboundGroupSessionPtr
|
mtx::crypto::InboundGroupSessionPtr
|
||||||
getInboundMegolmSession(const MegolmSessionIndex &index)
|
getInboundMegolmSession(const MegolmSessionIndex &index)
|
||||||
|
@ -4580,6 +4710,11 @@ inboundMegolmSessionExists(const MegolmSessionIndex &index)
|
||||||
{
|
{
|
||||||
return instance_->inboundMegolmSessionExists(index);
|
return instance_->inboundMegolmSessionExists(index);
|
||||||
}
|
}
|
||||||
|
std::optional<GroupSessionData>
|
||||||
|
getMegolmSessionData(const MegolmSessionIndex &index)
|
||||||
|
{
|
||||||
|
return instance_->getMegolmSessionData(index);
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Olm Sessions
|
// Olm Sessions
|
||||||
|
|
|
@ -200,7 +200,7 @@ isRoomMember(const std::string &user_id, const std::string &room_id);
|
||||||
//
|
//
|
||||||
void
|
void
|
||||||
saveOutboundMegolmSession(const std::string &room_id,
|
saveOutboundMegolmSession(const std::string &room_id,
|
||||||
const OutboundGroupSessionData &data,
|
const GroupSessionData &data,
|
||||||
mtx::crypto::OutboundGroupSessionPtr &session);
|
mtx::crypto::OutboundGroupSessionPtr &session);
|
||||||
OutboundGroupSessionDataRef
|
OutboundGroupSessionDataRef
|
||||||
getOutboundMegolmSession(const std::string &room_id);
|
getOutboundMegolmSession(const std::string &room_id);
|
||||||
|
@ -208,7 +208,7 @@ bool
|
||||||
outboundMegolmSessionExists(const std::string &room_id) noexcept;
|
outboundMegolmSessionExists(const std::string &room_id) noexcept;
|
||||||
void
|
void
|
||||||
updateOutboundMegolmSession(const std::string &room_id,
|
updateOutboundMegolmSession(const std::string &room_id,
|
||||||
const OutboundGroupSessionData &data,
|
const GroupSessionData &data,
|
||||||
mtx::crypto::OutboundGroupSessionPtr &session);
|
mtx::crypto::OutboundGroupSessionPtr &session);
|
||||||
void
|
void
|
||||||
dropOutboundMegolmSession(const std::string &room_id);
|
dropOutboundMegolmSession(const std::string &room_id);
|
||||||
|
@ -223,11 +223,14 @@ exportSessionKeys();
|
||||||
//
|
//
|
||||||
void
|
void
|
||||||
saveInboundMegolmSession(const MegolmSessionIndex &index,
|
saveInboundMegolmSession(const MegolmSessionIndex &index,
|
||||||
mtx::crypto::InboundGroupSessionPtr session);
|
mtx::crypto::InboundGroupSessionPtr session,
|
||||||
|
const GroupSessionData &data);
|
||||||
mtx::crypto::InboundGroupSessionPtr
|
mtx::crypto::InboundGroupSessionPtr
|
||||||
getInboundMegolmSession(const MegolmSessionIndex &index);
|
getInboundMegolmSession(const MegolmSessionIndex &index);
|
||||||
bool
|
bool
|
||||||
inboundMegolmSessionExists(const MegolmSessionIndex &index);
|
inboundMegolmSessionExists(const MegolmSessionIndex &index);
|
||||||
|
std::optional<GroupSessionData>
|
||||||
|
getMegolmSessionData(const MegolmSessionIndex &index);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Olm Sessions
|
// Olm Sessions
|
||||||
|
|
|
@ -27,40 +27,43 @@ enum Trust
|
||||||
Q_ENUM_NS(Trust)
|
Q_ENUM_NS(Trust)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct DeviceAndMasterKeys
|
struct DeviceKeysToMsgIndex
|
||||||
{
|
{
|
||||||
// map from device id or master key id to message_index
|
// map from device key to message_index
|
||||||
std::map<std::string, uint64_t> devices, master_keys;
|
// Using the device id is safe because we check for reuse on device list updates
|
||||||
|
// Using the device id makes our logic much easier to read.
|
||||||
|
std::map<std::string, uint64_t> deviceids;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SharedWithUsers
|
struct SharedWithUsers
|
||||||
{
|
{
|
||||||
// userid to keys
|
// userid to keys
|
||||||
std::map<std::string, DeviceAndMasterKeys> keys;
|
std::map<std::string, DeviceKeysToMsgIndex> keys;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Extra information associated with an outbound megolm session.
|
// Extra information associated with an outbound megolm session.
|
||||||
struct OutboundGroupSessionData
|
struct GroupSessionData
|
||||||
{
|
{
|
||||||
std::string session_id;
|
|
||||||
std::string session_key;
|
|
||||||
uint64_t message_index = 0;
|
uint64_t message_index = 0;
|
||||||
uint64_t timestamp = 0;
|
uint64_t timestamp = 0;
|
||||||
|
|
||||||
|
std::string sender_claimed_ed25519_key;
|
||||||
|
std::vector<std::string> forwarding_curve25519_key_chain;
|
||||||
|
|
||||||
// who has access to this session.
|
// who has access to this session.
|
||||||
// Rotate, when a user leaves the room and share, when a user gets added.
|
// Rotate, when a user leaves the room and share, when a user gets added.
|
||||||
SharedWithUsers initially, currently;
|
SharedWithUsers currently;
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
to_json(nlohmann::json &obj, const OutboundGroupSessionData &msg);
|
to_json(nlohmann::json &obj, const GroupSessionData &msg);
|
||||||
void
|
void
|
||||||
from_json(const nlohmann::json &obj, OutboundGroupSessionData &msg);
|
from_json(const nlohmann::json &obj, GroupSessionData &msg);
|
||||||
|
|
||||||
struct OutboundGroupSessionDataRef
|
struct OutboundGroupSessionDataRef
|
||||||
{
|
{
|
||||||
mtx::crypto::OutboundGroupSessionPtr session;
|
mtx::crypto::OutboundGroupSessionPtr session;
|
||||||
OutboundGroupSessionData data;
|
GroupSessionData data;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DevicePublicKeys
|
struct DevicePublicKeys
|
||||||
|
@ -134,6 +137,8 @@ struct UserKeyCache
|
||||||
bool master_key_changed = false;
|
bool master_key_changed = false;
|
||||||
//! Device keys that were already used at least once
|
//! Device keys that were already used at least once
|
||||||
std::set<std::string> seen_device_keys;
|
std::set<std::string> seen_device_keys;
|
||||||
|
//! Device ids that were already used at least once
|
||||||
|
std::set<std::string> seen_device_ids;
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <mtx/events/join_rules.hpp>
|
#include <mtx/events/join_rules.hpp>
|
||||||
|
#include <mtx/events/mscs/image_packs.hpp>
|
||||||
|
|
||||||
namespace cache {
|
namespace cache {
|
||||||
enum class CacheVersion : int
|
enum class CacheVersion : int
|
||||||
|
@ -109,3 +110,9 @@ struct RoomSearchResult
|
||||||
std::string room_id;
|
std::string room_id;
|
||||||
RoomInfo info;
|
RoomInfo info;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ImagePackInfo
|
||||||
|
{
|
||||||
|
std::string packname;
|
||||||
|
std::map<std::string, mtx::events::msc2545::PackImage> images;
|
||||||
|
};
|
||||||
|
|
|
@ -225,6 +225,8 @@ public:
|
||||||
std::vector<std::string> getParentRoomIds(const std::string &room_id);
|
std::vector<std::string> getParentRoomIds(const std::string &room_id);
|
||||||
std::vector<std::string> getChildRoomIds(const std::string &room_id);
|
std::vector<std::string> getChildRoomIds(const std::string &room_id);
|
||||||
|
|
||||||
|
std::vector<ImagePackInfo> getImagePacks(const std::string &room_id, bool stickers);
|
||||||
|
|
||||||
//! Mark a room that uses e2e encryption.
|
//! Mark a room that uses e2e encryption.
|
||||||
void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
|
void setEncryptedRoom(lmdb::txn &txn, const std::string &room_id);
|
||||||
bool isRoomEncrypted(const std::string &room_id);
|
bool isRoomEncrypted(const std::string &room_id);
|
||||||
|
@ -238,12 +240,12 @@ public:
|
||||||
// Outbound Megolm Sessions
|
// Outbound Megolm Sessions
|
||||||
//
|
//
|
||||||
void saveOutboundMegolmSession(const std::string &room_id,
|
void saveOutboundMegolmSession(const std::string &room_id,
|
||||||
const OutboundGroupSessionData &data,
|
const GroupSessionData &data,
|
||||||
mtx::crypto::OutboundGroupSessionPtr &session);
|
mtx::crypto::OutboundGroupSessionPtr &session);
|
||||||
OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id);
|
OutboundGroupSessionDataRef getOutboundMegolmSession(const std::string &room_id);
|
||||||
bool outboundMegolmSessionExists(const std::string &room_id) noexcept;
|
bool outboundMegolmSessionExists(const std::string &room_id) noexcept;
|
||||||
void updateOutboundMegolmSession(const std::string &room_id,
|
void updateOutboundMegolmSession(const std::string &room_id,
|
||||||
const OutboundGroupSessionData &data,
|
const GroupSessionData &data,
|
||||||
mtx::crypto::OutboundGroupSessionPtr &session);
|
mtx::crypto::OutboundGroupSessionPtr &session);
|
||||||
void dropOutboundMegolmSession(const std::string &room_id);
|
void dropOutboundMegolmSession(const std::string &room_id);
|
||||||
|
|
||||||
|
@ -254,10 +256,12 @@ public:
|
||||||
// Inbound Megolm Sessions
|
// Inbound Megolm Sessions
|
||||||
//
|
//
|
||||||
void saveInboundMegolmSession(const MegolmSessionIndex &index,
|
void saveInboundMegolmSession(const MegolmSessionIndex &index,
|
||||||
mtx::crypto::InboundGroupSessionPtr session);
|
mtx::crypto::InboundGroupSessionPtr session,
|
||||||
|
const GroupSessionData &data);
|
||||||
mtx::crypto::InboundGroupSessionPtr getInboundMegolmSession(
|
mtx::crypto::InboundGroupSessionPtr getInboundMegolmSession(
|
||||||
const MegolmSessionIndex &index);
|
const MegolmSessionIndex &index);
|
||||||
bool inboundMegolmSessionExists(const MegolmSessionIndex &index);
|
bool inboundMegolmSessionExists(const MegolmSessionIndex &index);
|
||||||
|
std::optional<GroupSessionData> getMegolmSessionData(const MegolmSessionIndex &index);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Olm Sessions
|
// Olm Sessions
|
||||||
|
@ -676,6 +680,7 @@ private:
|
||||||
|
|
||||||
lmdb::dbi inboundMegolmSessionDb_;
|
lmdb::dbi inboundMegolmSessionDb_;
|
||||||
lmdb::dbi outboundMegolmSessionDb_;
|
lmdb::dbi outboundMegolmSessionDb_;
|
||||||
|
lmdb::dbi megolmSessionDataDb_;
|
||||||
|
|
||||||
QString localUserId_;
|
QString localUserId_;
|
||||||
QString cacheDirectory_;
|
QString cacheDirectory_;
|
||||||
|
|
|
@ -939,12 +939,16 @@ ChatPage::ensureOneTimeKeyCount(const std::map<std::string, uint16_t> &counts)
|
||||||
[](const mtx::responses::UploadKeys &, mtx::http::RequestErr err) {
|
[](const mtx::responses::UploadKeys &, mtx::http::RequestErr err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
nhlog::crypto()->warn(
|
nhlog::crypto()->warn(
|
||||||
"failed to update one-time keys: {} {}",
|
"failed to update one-time keys: {} {} {}",
|
||||||
err->matrix_error.error,
|
err->matrix_error.error,
|
||||||
static_cast<int>(err->status_code));
|
static_cast<int>(err->status_code),
|
||||||
|
static_cast<int>(err->error_code));
|
||||||
|
|
||||||
|
if (err->status_code < 400 || err->status_code >= 500)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mark as published anyway, otherwise we may end up in a loop.
|
||||||
olm::mark_keys_as_published();
|
olm::mark_keys_as_published();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
74
src/ImagePackModel.cpp
Normal file
74
src/ImagePackModel.cpp
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "ImagePackModel.h"
|
||||||
|
|
||||||
|
#include "Cache_p.h"
|
||||||
|
#include "CompletionModelRoles.h"
|
||||||
|
|
||||||
|
ImagePackModel::ImagePackModel(const std::string &roomId, bool stickers, QObject *parent)
|
||||||
|
: QAbstractListModel(parent)
|
||||||
|
, room_id(roomId)
|
||||||
|
{
|
||||||
|
auto packs = cache::client()->getImagePacks(room_id, stickers);
|
||||||
|
|
||||||
|
for (const auto &pack : packs) {
|
||||||
|
QString packname = QString::fromStdString(pack.packname);
|
||||||
|
|
||||||
|
for (const auto &img : pack.images) {
|
||||||
|
ImageDesc i{};
|
||||||
|
i.shortcode = QString::fromStdString(img.first);
|
||||||
|
i.packname = packname;
|
||||||
|
i.image = img.second;
|
||||||
|
images.push_back(std::move(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ImagePackModel::rowCount(const QModelIndex &) const
|
||||||
|
{
|
||||||
|
return (int)images.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray>
|
||||||
|
ImagePackModel::roleNames() const
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
{CompletionModel::CompletionRole, "completionRole"},
|
||||||
|
{CompletionModel::SearchRole, "searchRole"},
|
||||||
|
{CompletionModel::SearchRole2, "searchRole2"},
|
||||||
|
{Roles::Url, "url"},
|
||||||
|
{Roles::ShortCode, "shortcode"},
|
||||||
|
{Roles::Body, "body"},
|
||||||
|
{Roles::PackName, "packname"},
|
||||||
|
{Roles::OriginalRow, "originalRow"},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant
|
||||||
|
ImagePackModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (hasIndex(index.row(), index.column(), index.parent())) {
|
||||||
|
switch (role) {
|
||||||
|
case CompletionModel::CompletionRole:
|
||||||
|
return QString::fromStdString(images[index.row()].image.url);
|
||||||
|
case Roles::Url:
|
||||||
|
return QString::fromStdString(images[index.row()].image.url);
|
||||||
|
case CompletionModel::SearchRole:
|
||||||
|
case Roles::ShortCode:
|
||||||
|
return images[index.row()].shortcode;
|
||||||
|
case CompletionModel::SearchRole2:
|
||||||
|
case Roles::Body:
|
||||||
|
return QString::fromStdString(images[index.row()].image.body);
|
||||||
|
case Roles::PackName:
|
||||||
|
return images[index.row()].packname;
|
||||||
|
case Roles::OriginalRow:
|
||||||
|
return index.row();
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
48
src/ImagePackModel.h
Normal file
48
src/ImagePackModel.h
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 Nheko Contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
|
||||||
|
#include <mtx/events/mscs/image_packs.hpp>
|
||||||
|
|
||||||
|
class ImagePackModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
enum Roles
|
||||||
|
{
|
||||||
|
Url = Qt::UserRole,
|
||||||
|
ShortCode,
|
||||||
|
Body,
|
||||||
|
PackName,
|
||||||
|
OriginalRow,
|
||||||
|
};
|
||||||
|
|
||||||
|
ImagePackModel(const std::string &roomId, bool stickers, QObject *parent = nullptr);
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
|
|
||||||
|
mtx::events::msc2545::PackImage imageAt(int row)
|
||||||
|
{
|
||||||
|
if (row < 0 || static_cast<size_t>(row) >= images.size())
|
||||||
|
return {};
|
||||||
|
return images.at(static_cast<size_t>(row)).image;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string room_id;
|
||||||
|
|
||||||
|
struct ImageDesc
|
||||||
|
{
|
||||||
|
QString shortcode;
|
||||||
|
QString packname;
|
||||||
|
|
||||||
|
mtx::events::msc2545::PackImage image;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<ImageDesc> images;
|
||||||
|
};
|
177
src/Olm.cpp
177
src/Olm.cpp
|
@ -123,7 +123,17 @@ handle_to_device_messages(const std::vector<mtx::events::collections::DeviceEven
|
||||||
if (msg_type == to_string(mtx::events::EventType::RoomEncrypted)) {
|
if (msg_type == to_string(mtx::events::EventType::RoomEncrypted)) {
|
||||||
try {
|
try {
|
||||||
olm::OlmMessage olm_msg = j_msg;
|
olm::OlmMessage olm_msg = j_msg;
|
||||||
handle_olm_message(std::move(olm_msg));
|
cache::client()->query_keys(
|
||||||
|
olm_msg.sender,
|
||||||
|
[olm_msg](const UserKeyCache &userKeys, mtx::http::RequestErr e) {
|
||||||
|
if (e) {
|
||||||
|
nhlog::crypto()->error(
|
||||||
|
"Failed to query user keys, dropping olm "
|
||||||
|
"message");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handle_olm_message(std::move(olm_msg), userKeys);
|
||||||
|
});
|
||||||
} catch (const nlohmann::json::exception &e) {
|
} catch (const nlohmann::json::exception &e) {
|
||||||
nhlog::crypto()->warn(
|
nhlog::crypto()->warn(
|
||||||
"parsing error for olm message: {} {}", e.what(), j_msg.dump(2));
|
"parsing error for olm message: {} {}", e.what(), j_msg.dump(2));
|
||||||
|
@ -197,7 +207,7 @@ handle_to_device_messages(const std::vector<mtx::events::collections::DeviceEven
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
handle_olm_message(const OlmMessage &msg)
|
handle_olm_message(const OlmMessage &msg, const UserKeyCache &otherUserDeviceKeys)
|
||||||
{
|
{
|
||||||
nhlog::crypto()->info("sender : {}", msg.sender);
|
nhlog::crypto()->info("sender : {}", msg.sender);
|
||||||
nhlog::crypto()->info("sender_key: {}", msg.sender_key);
|
nhlog::crypto()->info("sender_key: {}", msg.sender_key);
|
||||||
|
@ -209,7 +219,7 @@ handle_olm_message(const OlmMessage &msg)
|
||||||
if (cipher.first != my_key) {
|
if (cipher.first != my_key) {
|
||||||
nhlog::crypto()->debug(
|
nhlog::crypto()->debug(
|
||||||
"Skipping message for {} since we are {}.", cipher.first, my_key);
|
"Skipping message for {} since we are {}.", cipher.first, my_key);
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto type = cipher.second.type;
|
const auto type = cipher.second.type;
|
||||||
|
@ -231,6 +241,57 @@ handle_olm_message(const OlmMessage &msg)
|
||||||
if (!payload.is_null()) {
|
if (!payload.is_null()) {
|
||||||
mtx::events::collections::DeviceEvents device_event;
|
mtx::events::collections::DeviceEvents device_event;
|
||||||
|
|
||||||
|
// Other properties are included in order to prevent an attacker from
|
||||||
|
// publishing someone else's curve25519 keys as their own and subsequently
|
||||||
|
// claiming to have sent messages which they didn't. sender must correspond
|
||||||
|
// to the user who sent the event, recipient to the local user, and
|
||||||
|
// recipient_keys to the local ed25519 key.
|
||||||
|
std::string receiver_ed25519 = payload["recipient_keys"]["ed25519"];
|
||||||
|
if (receiver_ed25519.empty() ||
|
||||||
|
receiver_ed25519 != olm::client()->identity_keys().ed25519) {
|
||||||
|
nhlog::crypto()->warn(
|
||||||
|
"Decrypted event doesn't include our ed25519: {}",
|
||||||
|
payload.dump());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string receiver = payload["recipient"];
|
||||||
|
if (receiver.empty() || receiver != http::client()->user_id().to_string()) {
|
||||||
|
nhlog::crypto()->warn(
|
||||||
|
"Decrypted event doesn't include our user_id: {}",
|
||||||
|
payload.dump());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clients must confirm that the sender_key and the ed25519 field value
|
||||||
|
// under the keys property match the keys returned by /keys/query for the
|
||||||
|
// given user, and must also verify the signature of the payload. Without
|
||||||
|
// this check, a client cannot be sure that the sender device owns the
|
||||||
|
// private part of the ed25519 key it claims to have in the Olm payload.
|
||||||
|
// This is crucial when the ed25519 key corresponds to a verified device.
|
||||||
|
std::string sender_ed25519 = payload["keys"]["ed25519"];
|
||||||
|
if (sender_ed25519.empty()) {
|
||||||
|
nhlog::crypto()->warn(
|
||||||
|
"Decrypted event doesn't include sender ed25519: {}",
|
||||||
|
payload.dump());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool from_their_device = false;
|
||||||
|
for (auto [device_id, key] : otherUserDeviceKeys.device_keys) {
|
||||||
|
if (key.keys.at("curve25519:" + device_id) == msg.sender_key) {
|
||||||
|
if (key.keys.at("ed25519:" + device_id) == sender_ed25519) {
|
||||||
|
from_their_device = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!from_their_device) {
|
||||||
|
nhlog::crypto()->warn("Decrypted event isn't sent from a device "
|
||||||
|
"listed by that user! {}",
|
||||||
|
payload.dump());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
std::string msg_type = payload["type"];
|
std::string msg_type = payload["type"];
|
||||||
json event_array = json::array();
|
json event_array = json::array();
|
||||||
|
@ -242,7 +303,7 @@ handle_olm_message(const OlmMessage &msg)
|
||||||
if (temp_events.empty()) {
|
if (temp_events.empty()) {
|
||||||
nhlog::crypto()->warn("Decrypted unknown event: {}",
|
nhlog::crypto()->warn("Decrypted unknown event: {}",
|
||||||
payload.dump());
|
payload.dump());
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
device_event = temp_events.at(0);
|
device_event = temp_events.at(0);
|
||||||
}
|
}
|
||||||
|
@ -276,17 +337,20 @@ handle_olm_message(const OlmMessage &msg)
|
||||||
ChatPage::instance()->receivedDeviceVerificationDone(e8->content);
|
ChatPage::instance()->receivedDeviceVerificationDone(e8->content);
|
||||||
} else if (auto roomKey =
|
} else if (auto roomKey =
|
||||||
std::get_if<DeviceEvent<msg::RoomKey>>(&device_event)) {
|
std::get_if<DeviceEvent<msg::RoomKey>>(&device_event)) {
|
||||||
create_inbound_megolm_session(*roomKey, msg.sender_key);
|
create_inbound_megolm_session(
|
||||||
|
*roomKey, msg.sender_key, sender_ed25519);
|
||||||
} else if (auto forwardedRoomKey =
|
} else if (auto forwardedRoomKey =
|
||||||
std::get_if<DeviceEvent<msg::ForwardedRoomKey>>(
|
std::get_if<DeviceEvent<msg::ForwardedRoomKey>>(
|
||||||
&device_event)) {
|
&device_event)) {
|
||||||
|
forwardedRoomKey->content.forwarding_curve25519_key_chain.push_back(
|
||||||
|
msg.sender_key);
|
||||||
import_inbound_megolm_session(*forwardedRoomKey);
|
import_inbound_megolm_session(*forwardedRoomKey);
|
||||||
} else if (auto e =
|
} else if (auto e =
|
||||||
std::get_if<DeviceEvent<msg::SecretSend>>(&device_event)) {
|
std::get_if<DeviceEvent<msg::SecretSend>>(&device_event)) {
|
||||||
auto local_user = http::client()->user_id();
|
auto local_user = http::client()->user_id();
|
||||||
|
|
||||||
if (msg.sender != local_user.to_string())
|
if (msg.sender != local_user.to_string())
|
||||||
continue;
|
return;
|
||||||
|
|
||||||
auto secret_name =
|
auto secret_name =
|
||||||
request_id_to_secret_name.find(e->content.request_id);
|
request_id_to_secret_name.find(e->content.request_id);
|
||||||
|
@ -306,7 +370,7 @@ handle_olm_message(const OlmMessage &msg)
|
||||||
cache::verificationStatus(local_user.to_string());
|
cache::verificationStatus(local_user.to_string());
|
||||||
|
|
||||||
if (!verificationStatus)
|
if (!verificationStatus)
|
||||||
continue;
|
return;
|
||||||
|
|
||||||
auto deviceKeys = cache::userKeys(local_user.to_string());
|
auto deviceKeys = cache::userKeys(local_user.to_string());
|
||||||
std::string sender_device_id;
|
std::string sender_device_id;
|
||||||
|
@ -344,7 +408,6 @@ handle_olm_message(const OlmMessage &msg)
|
||||||
"for secrect "
|
"for secrect "
|
||||||
"'{}'",
|
"'{}'",
|
||||||
name);
|
name);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -364,13 +427,8 @@ handle_olm_message(const OlmMessage &msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto otherUserDeviceKeys = cache::userKeys(msg.sender);
|
|
||||||
|
|
||||||
if (!otherUserDeviceKeys)
|
|
||||||
return;
|
|
||||||
|
|
||||||
std::map<std::string, std::vector<std::string>> targets;
|
std::map<std::string, std::vector<std::string>> targets;
|
||||||
for (auto [device_id, key] : otherUserDeviceKeys->device_keys) {
|
for (auto [device_id, key] : otherUserDeviceKeys.device_keys) {
|
||||||
if (key.keys.at("curve25519:" + device_id) == msg.sender_key)
|
if (key.keys.at("curve25519:" + device_id) == msg.sender_key)
|
||||||
targets[msg.sender].push_back(device_id);
|
targets[msg.sender].push_back(device_id);
|
||||||
}
|
}
|
||||||
|
@ -450,7 +508,7 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
|
||||||
|
|
||||||
std::map<std::string, std::vector<std::string>> sendSessionTo;
|
std::map<std::string, std::vector<std::string>> sendSessionTo;
|
||||||
mtx::crypto::OutboundGroupSessionPtr session = nullptr;
|
mtx::crypto::OutboundGroupSessionPtr session = nullptr;
|
||||||
OutboundGroupSessionData group_session_data;
|
GroupSessionData group_session_data;
|
||||||
|
|
||||||
if (cache::outboundMegolmSessionExists(room_id)) {
|
if (cache::outboundMegolmSessionExists(room_id)) {
|
||||||
auto res = cache::getOutboundMegolmSession(room_id);
|
auto res = cache::getOutboundMegolmSession(room_id);
|
||||||
|
@ -519,7 +577,8 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
|
||||||
} else {
|
} else {
|
||||||
// compare devices
|
// compare devices
|
||||||
bool device_removed = false;
|
bool device_removed = false;
|
||||||
for (const auto &dev : session_member_it->second.devices) {
|
for (const auto &dev :
|
||||||
|
session_member_it->second.deviceids) {
|
||||||
if (!member_it->second ||
|
if (!member_it->second ||
|
||||||
!member_it->second->device_keys.count(
|
!member_it->second->device_keys.count(
|
||||||
dev.first)) {
|
dev.first)) {
|
||||||
|
@ -541,7 +600,7 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
|
||||||
if (member_it->second)
|
if (member_it->second)
|
||||||
for (const auto &dev :
|
for (const auto &dev :
|
||||||
member_it->second->device_keys)
|
member_it->second->device_keys)
|
||||||
if (!session_member_it->second.devices
|
if (!session_member_it->second.deviceids
|
||||||
.count(dev.first) &&
|
.count(dev.first) &&
|
||||||
(member_it->first != own_user_id ||
|
(member_it->first != own_user_id ||
|
||||||
dev.first != device_id))
|
dev.first != device_id))
|
||||||
|
@ -571,32 +630,28 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
|
||||||
const auto session_key = mtx::crypto::session_key(session.get());
|
const auto session_key = mtx::crypto::session_key(session.get());
|
||||||
|
|
||||||
// Saving the new megolm session.
|
// Saving the new megolm session.
|
||||||
OutboundGroupSessionData session_data{};
|
GroupSessionData session_data{};
|
||||||
session_data.session_id = mtx::crypto::session_id(session.get());
|
|
||||||
session_data.session_key = mtx::crypto::session_key(session.get());
|
|
||||||
session_data.message_index = 0;
|
session_data.message_index = 0;
|
||||||
session_data.timestamp = QDateTime::currentMSecsSinceEpoch();
|
session_data.timestamp = QDateTime::currentMSecsSinceEpoch();
|
||||||
|
session_data.sender_claimed_ed25519_key = olm::client()->identity_keys().ed25519;
|
||||||
|
|
||||||
sendSessionTo.clear();
|
sendSessionTo.clear();
|
||||||
|
|
||||||
for (const auto &[user, devices] : members) {
|
for (const auto &[user, devices] : members) {
|
||||||
sendSessionTo[user] = {};
|
sendSessionTo[user] = {};
|
||||||
session_data.initially.keys[user] = {};
|
session_data.currently.keys[user] = {};
|
||||||
if (devices) {
|
if (devices) {
|
||||||
for (const auto &[device_id_, key] : devices->device_keys) {
|
for (const auto &[device_id_, key] : devices->device_keys) {
|
||||||
(void)key;
|
(void)key;
|
||||||
if (device_id != device_id_ || user != own_user_id) {
|
if (device_id != device_id_ || user != own_user_id) {
|
||||||
sendSessionTo[user].push_back(device_id_);
|
sendSessionTo[user].push_back(device_id_);
|
||||||
session_data.initially.keys[user]
|
session_data.currently.keys[user]
|
||||||
.devices[device_id_] = 0;
|
.deviceids[device_id_] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cache::saveOutboundMegolmSession(room_id, session_data, session);
|
|
||||||
group_session_data = std::move(session_data);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
MegolmSessionIndex index;
|
MegolmSessionIndex index;
|
||||||
index.room_id = room_id;
|
index.room_id = room_id;
|
||||||
|
@ -604,8 +659,12 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
|
||||||
index.sender_key = olm::client()->identity_keys().curve25519;
|
index.sender_key = olm::client()->identity_keys().curve25519;
|
||||||
auto megolm_session =
|
auto megolm_session =
|
||||||
olm::client()->init_inbound_group_session(session_key);
|
olm::client()->init_inbound_group_session(session_key);
|
||||||
cache::saveInboundMegolmSession(index, std::move(megolm_session));
|
cache::saveInboundMegolmSession(
|
||||||
|
index, std::move(megolm_session), session_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cache::saveOutboundMegolmSession(room_id, session_data, session);
|
||||||
|
group_session_data = std::move(session_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
mtx::events::DeviceEvent<mtx::events::msg::RoomKey> megolm_payload{};
|
mtx::events::DeviceEvent<mtx::events::msg::RoomKey> megolm_payload{};
|
||||||
|
@ -641,8 +700,8 @@ encrypt_group_message(const std::string &room_id, const std::string &device_id,
|
||||||
group_session_data.currently.keys[user] = {};
|
group_session_data.currently.keys[user] = {};
|
||||||
|
|
||||||
for (const auto &device_id_ : devices) {
|
for (const auto &device_id_ : devices) {
|
||||||
if (!group_session_data.currently.keys[user].devices.count(device_id_))
|
if (!group_session_data.currently.keys[user].deviceids.count(device_id_))
|
||||||
group_session_data.currently.keys[user].devices[device_id_] =
|
group_session_data.currently.keys[user].deviceids[device_id_] =
|
||||||
group_session_data.message_index;
|
group_session_data.message_index;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -704,7 +763,8 @@ try_olm_decryption(const std::string &sender_key, const mtx::events::msg::OlmCip
|
||||||
|
|
||||||
void
|
void
|
||||||
create_inbound_megolm_session(const mtx::events::DeviceEvent<mtx::events::msg::RoomKey> &roomKey,
|
create_inbound_megolm_session(const mtx::events::DeviceEvent<mtx::events::msg::RoomKey> &roomKey,
|
||||||
const std::string &sender_key)
|
const std::string &sender_key,
|
||||||
|
const std::string &sender_ed25519)
|
||||||
{
|
{
|
||||||
MegolmSessionIndex index;
|
MegolmSessionIndex index;
|
||||||
index.room_id = roomKey.content.room_id;
|
index.room_id = roomKey.content.room_id;
|
||||||
|
@ -712,9 +772,13 @@ create_inbound_megolm_session(const mtx::events::DeviceEvent<mtx::events::msg::R
|
||||||
index.sender_key = sender_key;
|
index.sender_key = sender_key;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
GroupSessionData data{};
|
||||||
|
data.forwarding_curve25519_key_chain = {sender_key};
|
||||||
|
data.sender_claimed_ed25519_key = sender_ed25519;
|
||||||
|
|
||||||
auto megolm_session =
|
auto megolm_session =
|
||||||
olm::client()->init_inbound_group_session(roomKey.content.session_key);
|
olm::client()->init_inbound_group_session(roomKey.content.session_key);
|
||||||
cache::saveInboundMegolmSession(index, std::move(megolm_session));
|
cache::saveInboundMegolmSession(index, std::move(megolm_session), data);
|
||||||
} catch (const lmdb::error &e) {
|
} catch (const lmdb::error &e) {
|
||||||
nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what());
|
nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what());
|
||||||
return;
|
return;
|
||||||
|
@ -741,7 +805,13 @@ import_inbound_megolm_session(
|
||||||
try {
|
try {
|
||||||
auto megolm_session =
|
auto megolm_session =
|
||||||
olm::client()->import_inbound_group_session(roomKey.content.session_key);
|
olm::client()->import_inbound_group_session(roomKey.content.session_key);
|
||||||
cache::saveInboundMegolmSession(index, std::move(megolm_session));
|
|
||||||
|
GroupSessionData data{};
|
||||||
|
data.forwarding_curve25519_key_chain =
|
||||||
|
roomKey.content.forwarding_curve25519_key_chain;
|
||||||
|
data.sender_claimed_ed25519_key = roomKey.content.sender_claimed_ed25519_key;
|
||||||
|
|
||||||
|
cache::saveInboundMegolmSession(index, std::move(megolm_session), data);
|
||||||
} catch (const lmdb::error &e) {
|
} catch (const lmdb::error &e) {
|
||||||
nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what());
|
nhlog::crypto()->critical("failed to save inbound megolm session: {}", e.what());
|
||||||
return;
|
return;
|
||||||
|
@ -817,9 +887,12 @@ handle_key_request_message(const mtx::events::DeviceEvent<mtx::events::msg::KeyR
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we were the sender of the session being requested.
|
// Check if we were the sender of the session being requested (unless it is actually us
|
||||||
if (req.content.sender_key != olm::client()->identity_keys().curve25519) {
|
// requesting the session).
|
||||||
nhlog::crypto()->debug("ignoring key request {} because we were not the sender: "
|
if (req.sender != http::client()->user_id().to_string() &&
|
||||||
|
req.content.sender_key != olm::client()->identity_keys().curve25519) {
|
||||||
|
nhlog::crypto()->debug(
|
||||||
|
"ignoring key request {} because we did not create the requested session: "
|
||||||
"\nrequested({}) ours({})",
|
"\nrequested({}) ours({})",
|
||||||
req.content.request_id,
|
req.content.request_id,
|
||||||
req.content.sender_key,
|
req.content.sender_key,
|
||||||
|
@ -827,19 +900,19 @@ handle_key_request_message(const mtx::events::DeviceEvent<mtx::events::msg::KeyR
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we have the keys for the requested session.
|
|
||||||
auto outboundSession = cache::getOutboundMegolmSession(req.content.room_id);
|
|
||||||
if (!outboundSession.session) {
|
|
||||||
nhlog::crypto()->warn("requested session not found in room: {}",
|
|
||||||
req.content.room_id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the requested session_id and the one we have saved match.
|
// Check that the requested session_id and the one we have saved match.
|
||||||
MegolmSessionIndex index{};
|
MegolmSessionIndex index{};
|
||||||
index.room_id = req.content.room_id;
|
index.room_id = req.content.room_id;
|
||||||
index.session_id = req.content.session_id;
|
index.session_id = req.content.session_id;
|
||||||
index.sender_key = olm::client()->identity_keys().curve25519;
|
index.sender_key = req.content.sender_key;
|
||||||
|
|
||||||
|
// Check if we have the keys for the requested session.
|
||||||
|
auto sessionData = cache::getMegolmSessionData(index);
|
||||||
|
if (!sessionData) {
|
||||||
|
nhlog::crypto()->warn("requested session not found in room: {}",
|
||||||
|
req.content.room_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const auto session = cache::getInboundMegolmSession(index);
|
const auto session = cache::getInboundMegolmSession(index);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
|
@ -873,12 +946,12 @@ handle_key_request_message(const mtx::events::DeviceEvent<mtx::events::msg::KeyR
|
||||||
|
|
||||||
bool shouldSeeKeys = false;
|
bool shouldSeeKeys = false;
|
||||||
uint64_t minimumIndex = -1;
|
uint64_t minimumIndex = -1;
|
||||||
if (outboundSession.data.currently.keys.count(req.sender)) {
|
if (sessionData->currently.keys.count(req.sender)) {
|
||||||
if (outboundSession.data.currently.keys.at(req.sender)
|
if (sessionData->currently.keys.at(req.sender)
|
||||||
.devices.count(req.content.requesting_device_id)) {
|
.deviceids.count(req.content.requesting_device_id)) {
|
||||||
shouldSeeKeys = true;
|
shouldSeeKeys = true;
|
||||||
minimumIndex = outboundSession.data.currently.keys.at(req.sender)
|
minimumIndex = sessionData->currently.keys.at(req.sender)
|
||||||
.devices.at(req.content.requesting_device_id);
|
.deviceids.at(req.content.requesting_device_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -907,8 +980,9 @@ handle_key_request_message(const mtx::events::DeviceEvent<mtx::events::msg::KeyR
|
||||||
forward_key.sender_key = index.sender_key;
|
forward_key.sender_key = index.sender_key;
|
||||||
|
|
||||||
// TODO(Nico): Figure out if this is correct
|
// TODO(Nico): Figure out if this is correct
|
||||||
forward_key.sender_claimed_ed25519_key = olm::client()->identity_keys().ed25519;
|
forward_key.sender_claimed_ed25519_key = sessionData->sender_claimed_ed25519_key;
|
||||||
forward_key.forwarding_curve25519_key_chain = {};
|
forward_key.forwarding_curve25519_key_chain =
|
||||||
|
sessionData->forwarding_curve25519_key_chain;
|
||||||
|
|
||||||
send_megolm_key_to_device(
|
send_megolm_key_to_device(
|
||||||
req.sender, req.content.requesting_device_id, forward_key);
|
req.sender, req.content.requesting_device_id, forward_key);
|
||||||
|
@ -929,6 +1003,7 @@ send_megolm_key_to_device(const std::string &user_id,
|
||||||
std::map<std::string, std::vector<std::string>> targets;
|
std::map<std::string, std::vector<std::string>> targets;
|
||||||
targets[user_id] = {device_id};
|
targets[user_id] = {device_id};
|
||||||
send_encrypted_to_device_messages(targets, room_key);
|
send_encrypted_to_device_messages(targets, room_key);
|
||||||
|
nhlog::crypto()->debug("Forwarded key to {}:{}", user_id, device_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
DecryptionResult
|
DecryptionResult
|
||||||
|
|
|
@ -59,12 +59,13 @@ try_olm_decryption(const std::string &sender_key,
|
||||||
const mtx::events::msg::OlmCipherContent &content);
|
const mtx::events::msg::OlmCipherContent &content);
|
||||||
|
|
||||||
void
|
void
|
||||||
handle_olm_message(const OlmMessage &msg);
|
handle_olm_message(const OlmMessage &msg, const UserKeyCache &otherUserDeviceKeys);
|
||||||
|
|
||||||
//! Establish a new inbound megolm session with the decrypted payload from olm.
|
//! Establish a new inbound megolm session with the decrypted payload from olm.
|
||||||
void
|
void
|
||||||
create_inbound_megolm_session(const mtx::events::DeviceEvent<mtx::events::msg::RoomKey> &roomKey,
|
create_inbound_megolm_session(const mtx::events::DeviceEvent<mtx::events::msg::RoomKey> &roomKey,
|
||||||
const std::string &sender_key);
|
const std::string &sender_key,
|
||||||
|
const std::string &sender_ed25519);
|
||||||
void
|
void
|
||||||
import_inbound_megolm_session(
|
import_inbound_megolm_session(
|
||||||
const mtx::events::DeviceEvent<mtx::events::msg::ForwardedRoomKey> &roomKey);
|
const mtx::events::DeviceEvent<mtx::events::msg::ForwardedRoomKey> &roomKey);
|
||||||
|
|
|
@ -91,7 +91,7 @@ UserSettings::load(std::optional<QString> profile)
|
||||||
privacyScreen_ = settings.value("user/privacy_screen", false).toBool();
|
privacyScreen_ = settings.value("user/privacy_screen", false).toBool();
|
||||||
privacyScreenTimeout_ = settings.value("user/privacy_screen_timeout", 0).toInt();
|
privacyScreenTimeout_ = settings.value("user/privacy_screen_timeout", 0).toInt();
|
||||||
shareKeysWithTrustedUsers_ =
|
shareKeysWithTrustedUsers_ =
|
||||||
settings.value("user/share_keys_with_trusted_users", true).toBool();
|
settings.value("user/automatically_share_keys_with_trusted_users", false).toBool();
|
||||||
mobileMode_ = settings.value("user/mobile_mode", false).toBool();
|
mobileMode_ = settings.value("user/mobile_mode", false).toBool();
|
||||||
emojiFont_ = settings.value("user/emoji_font_family", "default").toString();
|
emojiFont_ = settings.value("user/emoji_font_family", "default").toString();
|
||||||
baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble();
|
baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble();
|
||||||
|
@ -610,7 +610,8 @@ UserSettings::save()
|
||||||
settings.setValue("decrypt_sidebar", decryptSidebar_);
|
settings.setValue("decrypt_sidebar", decryptSidebar_);
|
||||||
settings.setValue("privacy_screen", privacyScreen_);
|
settings.setValue("privacy_screen", privacyScreen_);
|
||||||
settings.setValue("privacy_screen_timeout", privacyScreenTimeout_);
|
settings.setValue("privacy_screen_timeout", privacyScreenTimeout_);
|
||||||
settings.setValue("share_keys_with_trusted_users", shareKeysWithTrustedUsers_);
|
settings.setValue("automatically_share_keys_with_trusted_users",
|
||||||
|
shareKeysWithTrustedUsers_);
|
||||||
settings.setValue("mobile_mode", mobileMode_);
|
settings.setValue("mobile_mode", mobileMode_);
|
||||||
settings.setValue("font_size", baseFontSize_);
|
settings.setValue("font_size", baseFontSize_);
|
||||||
settings.setValue("typing_notifications", typingNotifications_);
|
settings.setValue("typing_notifications", typingNotifications_);
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include "ChatPage.h"
|
#include "ChatPage.h"
|
||||||
#include "CompletionProxyModel.h"
|
#include "CompletionProxyModel.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
|
#include "ImagePackModel.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
#include "MatrixClient.h"
|
#include "MatrixClient.h"
|
||||||
|
@ -501,6 +502,31 @@ InputBar::video(const QString &filename,
|
||||||
room->sendMessageEvent(video, mtx::events::EventType::RoomMessage);
|
room->sendMessageEvent(video, mtx::events::EventType::RoomMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
InputBar::sticker(ImagePackModel *model, int row)
|
||||||
|
{
|
||||||
|
if (!model || row < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto img = model->imageAt(row);
|
||||||
|
|
||||||
|
mtx::events::msg::StickerImage sticker{};
|
||||||
|
sticker.info = img.info.value_or(mtx::common::ImageInfo{});
|
||||||
|
sticker.url = img.url;
|
||||||
|
sticker.body = img.body;
|
||||||
|
|
||||||
|
if (!room->reply().isEmpty()) {
|
||||||
|
sticker.relations.relations.push_back(
|
||||||
|
{mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
|
||||||
|
}
|
||||||
|
if (!room->edit().isEmpty()) {
|
||||||
|
sticker.relations.relations.push_back(
|
||||||
|
{mtx::common::RelationType::Replace, room->edit().toStdString()});
|
||||||
|
}
|
||||||
|
|
||||||
|
room->sendMessageEvent(sticker, mtx::events::EventType::Sticker);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
InputBar::command(QString command, QString args)
|
InputBar::command(QString command, QString args)
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include <mtx/responses/messages.hpp>
|
#include <mtx/responses/messages.hpp>
|
||||||
|
|
||||||
class TimelineModel;
|
class TimelineModel;
|
||||||
|
class ImagePackModel;
|
||||||
class QMimeData;
|
class QMimeData;
|
||||||
class QDropEvent;
|
class QDropEvent;
|
||||||
class QStringList;
|
class QStringList;
|
||||||
|
@ -57,6 +58,7 @@ public slots:
|
||||||
MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED,
|
MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED,
|
||||||
bool rainbowify = false);
|
bool rainbowify = false);
|
||||||
void reaction(const QString &reactedEvent, const QString &reactionKey);
|
void reaction(const QString &reactedEvent, const QString &reactionKey);
|
||||||
|
void sticker(ImagePackModel *model, int row);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void startTyping();
|
void startTyping();
|
||||||
|
|
|
@ -710,6 +710,14 @@ TimelineModel::data(const QModelIndex &index, int role) const
|
||||||
return data(*event, role);
|
return data(*event, role);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVariant
|
||||||
|
TimelineModel::dataById(QString id, int role, QString relatedTo)
|
||||||
|
{
|
||||||
|
if (auto event = events.get(id.toStdString(), relatedTo.toStdString()))
|
||||||
|
return data(*event, role);
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
TimelineModel::canFetchMore(const QModelIndex &) const
|
TimelineModel::canFetchMore(const QModelIndex &) const
|
||||||
{
|
{
|
||||||
|
@ -1292,6 +1300,14 @@ struct SendMessageVisitor
|
||||||
sendRoomEvent<mtx::events::msg::KeyVerificationCancel,
|
sendRoomEvent<mtx::events::msg::KeyVerificationCancel,
|
||||||
mtx::events::EventType::KeyVerificationCancel>(msg);
|
mtx::events::EventType::KeyVerificationCancel>(msg);
|
||||||
}
|
}
|
||||||
|
void operator()(mtx::events::Sticker msg)
|
||||||
|
{
|
||||||
|
msg.type = mtx::events::EventType::Sticker;
|
||||||
|
if (cache::isRoomEncrypted(model_->room_id_.toStdString())) {
|
||||||
|
model_->sendEncryptedMessage(msg, mtx::events::EventType::Sticker);
|
||||||
|
} else
|
||||||
|
emit model_->addPendingMessageToStore(msg);
|
||||||
|
}
|
||||||
|
|
||||||
TimelineModel *model_;
|
TimelineModel *model_;
|
||||||
};
|
};
|
||||||
|
@ -1301,6 +1317,7 @@ TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event)
|
||||||
{
|
{
|
||||||
std::visit(
|
std::visit(
|
||||||
[](auto &msg) {
|
[](auto &msg) {
|
||||||
|
// gets overwritten for reactions and stickers in SendMessageVisitor
|
||||||
msg.type = mtx::events::EventType::RoomMessage;
|
msg.type = mtx::events::EventType::RoomMessage;
|
||||||
msg.event_id = "m" + http::client()->generate_txn_id();
|
msg.event_id = "m" + http::client()->generate_txn_id();
|
||||||
msg.sender = http::client()->user_id().to_string();
|
msg.sender = http::client()->user_id().to_string();
|
||||||
|
|
|
@ -215,10 +215,7 @@ public:
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
QVariant data(const mtx::events::collections::TimelineEvents &event, int role) const;
|
QVariant data(const mtx::events::collections::TimelineEvents &event, int role) const;
|
||||||
Q_INVOKABLE QVariant dataById(QString id, int role)
|
Q_INVOKABLE QVariant dataById(QString id, int role, QString relatedTo);
|
||||||
{
|
|
||||||
return data(index(idToIndex(id)), role);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool canFetchMore(const QModelIndex &) const override;
|
bool canFetchMore(const QModelIndex &) const override;
|
||||||
void fetchMore(const QModelIndex &) override;
|
void fetchMore(const QModelIndex &) override;
|
||||||
|
@ -413,10 +410,17 @@ template<class T>
|
||||||
void
|
void
|
||||||
TimelineModel::sendMessageEvent(const T &content, mtx::events::EventType eventType)
|
TimelineModel::sendMessageEvent(const T &content, mtx::events::EventType eventType)
|
||||||
{
|
{
|
||||||
|
if constexpr (std::is_same_v<T, mtx::events::msg::StickerImage>) {
|
||||||
|
mtx::events::Sticker msgCopy = {};
|
||||||
|
msgCopy.content = content;
|
||||||
|
msgCopy.type = eventType;
|
||||||
|
emit newMessageToSend(msgCopy);
|
||||||
|
} else {
|
||||||
mtx::events::RoomEvent<T> msgCopy = {};
|
mtx::events::RoomEvent<T> msgCopy = {};
|
||||||
msgCopy.content = content;
|
msgCopy.content = content;
|
||||||
msgCopy.type = eventType;
|
msgCopy.type = eventType;
|
||||||
emit newMessageToSend(msgCopy);
|
emit newMessageToSend(msgCopy);
|
||||||
|
}
|
||||||
resetReply();
|
resetReply();
|
||||||
resetEdit();
|
resetEdit();
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include "DelegateChooser.h"
|
#include "DelegateChooser.h"
|
||||||
#include "DeviceVerificationFlow.h"
|
#include "DeviceVerificationFlow.h"
|
||||||
#include "EventAccessors.h"
|
#include "EventAccessors.h"
|
||||||
|
#include "ImagePackModel.h"
|
||||||
#include "Logging.h"
|
#include "Logging.h"
|
||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
#include "MatrixClient.h"
|
#include "MatrixClient.h"
|
||||||
|
@ -144,6 +145,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
|
||||||
qRegisterMetaType<mtx::events::msg::KeyVerificationReady>();
|
qRegisterMetaType<mtx::events::msg::KeyVerificationReady>();
|
||||||
qRegisterMetaType<mtx::events::msg::KeyVerificationRequest>();
|
qRegisterMetaType<mtx::events::msg::KeyVerificationRequest>();
|
||||||
qRegisterMetaType<mtx::events::msg::KeyVerificationStart>();
|
qRegisterMetaType<mtx::events::msg::KeyVerificationStart>();
|
||||||
|
qRegisterMetaType<ImagePackModel *>();
|
||||||
|
|
||||||
qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject,
|
qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject,
|
||||||
"im.nheko",
|
"im.nheko",
|
||||||
|
@ -593,6 +595,11 @@ TimelineViewManager::completerFor(QString completerName, QString roomId)
|
||||||
auto proxy = new CompletionProxyModel(roomModel);
|
auto proxy = new CompletionProxyModel(roomModel);
|
||||||
roomModel->setParent(proxy);
|
roomModel->setParent(proxy);
|
||||||
return proxy;
|
return proxy;
|
||||||
|
} else if (completerName == "stickers") {
|
||||||
|
auto stickerModel = new ImagePackModel(roomId.toStdString(), true);
|
||||||
|
auto proxy = new CompletionProxyModel(stickerModel, 1, static_cast<size_t>(-1) / 4);
|
||||||
|
stickerModel->setParent(proxy);
|
||||||
|
return proxy;
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue