Add basic encryption support
Sorry this is a huge commit, this actually includes a ton of stuff. Text color is now readable, multiple accounts are supported alongside end-to-end encryption but no cross-signing yet :-) There's also a whole lot of other small changes, such as choosing the server you want to request a room directory from.
This commit is contained in:
parent
303001a357
commit
a825c8886d
26 changed files with 1869 additions and 570 deletions
12
CMakeLists.txt
Normal file → Executable file
12
CMakeLists.txt
Normal file → Executable file
|
@ -1,11 +1,10 @@
|
|||
cmake_minimum_required(VERSION 2.8.12)
|
||||
project(Trinity LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
find_package(Qt5 COMPONENTS Core Quick Widgets REQUIRED)
|
||||
find_package(Qt5 COMPONENTS Core Quick Widgets WebEngine REQUIRED)
|
||||
|
||||
add_executable(${PROJECT_NAME}
|
||||
src/main.cpp
|
||||
|
@ -29,9 +28,14 @@ add_executable(${PROJECT_NAME}
|
|||
include/roomlistsortmodel.h
|
||||
include/emotelistmodel.h
|
||||
src/emotelistmodel.cpp
|
||||
include/emote.h)
|
||||
include/emote.h
|
||||
include/appcore.h
|
||||
include/encryption.h
|
||||
src/encryption.cpp)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Quick Qt5::Widgets cmark)
|
||||
find_package(Olm REQUIRED)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Quick Qt5::Widgets Qt5::WebEngine Olm::Olm cmark)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE include)
|
||||
|
||||
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
|
||||
|
|
25
include/appcore.h
Executable file
25
include/appcore.h
Executable file
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <vector>
|
||||
#include <QQmlContext>
|
||||
|
||||
class MatrixCore;
|
||||
|
||||
class AppCore : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QVariantList accounts READ getAccounts NOTIFY accountChange)
|
||||
public:
|
||||
Q_INVOKABLE void addAccount(QString profileName = "");
|
||||
|
||||
Q_INVOKABLE void switchAccount(QString profileName);
|
||||
|
||||
Q_INVOKABLE QVariantList getAccounts();
|
||||
|
||||
QList<MatrixCore*> accounts;
|
||||
|
||||
QQmlContext* context = nullptr;
|
||||
|
||||
signals:
|
||||
void accountChange();
|
||||
};
|
8
include/desktop.h
Normal file → Executable file
8
include/desktop.h
Normal file → Executable file
|
@ -11,14 +11,18 @@ public:
|
|||
QApplication::setQuitOnLastWindowClosed(shouldHide);
|
||||
|
||||
if(shouldHide)
|
||||
icon->hide();
|
||||
else
|
||||
icon->show();
|
||||
else
|
||||
icon->hide();
|
||||
}
|
||||
|
||||
Q_INVOKABLE void showMessage(const QString title, const QString content) {
|
||||
icon->showMessage(title, content);
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool isTrayIconEnabled() {
|
||||
return icon->isVisible();;
|
||||
}
|
||||
|
||||
QSystemTrayIcon* icon;
|
||||
};
|
||||
|
|
43
include/encryption.h
Executable file
43
include/encryption.h
Executable file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
|
||||
#include <olm/olm.h>
|
||||
|
||||
class Encryption {
|
||||
public:
|
||||
void createNewDeviceKeys();
|
||||
QJsonObject generateOneTimeKeys(int number);
|
||||
int getRecommendedNumberOfOneTimeKeys();
|
||||
|
||||
QString saveDeviceKeys();
|
||||
void loadDeviceKeys(QString bytes);
|
||||
|
||||
OlmOutboundGroupSession* beginOutboundSession();
|
||||
std::string getGroupSessionId(OlmOutboundGroupSession* session);
|
||||
std::string getGroupSessionKey(OlmOutboundGroupSession* session);
|
||||
OlmSession* beginOutboundOlmSession(std::string identityKey, std::string oneTimeKey);
|
||||
std::string getSessionId(OlmSession* session);
|
||||
std::string encrypt(OlmSession* session, std::string message);
|
||||
std::string encryptGroup(OlmOutboundGroupSession* session, std::string message);
|
||||
|
||||
QString saveSession(OlmOutboundGroupSession* session);
|
||||
OlmOutboundGroupSession* loadSession(QString bytes);
|
||||
|
||||
// inbound messaging
|
||||
OlmSession* createInboundSession(std::string senderKey, std::string body);
|
||||
std::vector<std::uint8_t> decrypt(OlmSession* session, int msgType, std::string cipherText);
|
||||
|
||||
OlmInboundGroupSession* beginInboundSession(std::string sessionKey);
|
||||
std::vector<std::uint8_t> decrypt(OlmInboundGroupSession* session, std::string cipherText);
|
||||
|
||||
QString signMessage(QString message);
|
||||
|
||||
QJsonObject identityKey;
|
||||
|
||||
private:
|
||||
void initAccount();
|
||||
|
||||
OlmAccount* account = nullptr;
|
||||
};
|
31
include/matrixcore.h
Normal file → Executable file
31
include/matrixcore.h
Normal file → Executable file
|
@ -12,16 +12,21 @@
|
|||
#include "roomlistsortmodel.h"
|
||||
#include "emote.h"
|
||||
#include "emotelistmodel.h"
|
||||
#include "encryption.h"
|
||||
|
||||
class Network;
|
||||
|
||||
class MatrixCore : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString profileName MEMBER profileName CONSTANT)
|
||||
Q_PROPERTY(bool initialSyncComplete READ isInitialSyncComplete NOTIFY initialSyncFinished)
|
||||
Q_PROPERTY(EventModel* eventModel READ getEventModel NOTIFY currentRoomChanged)
|
||||
Q_PROPERTY(RoomListSortModel* roomListModel READ getRoomListModel NOTIFY roomListChanged)
|
||||
Q_PROPERTY(Room* currentRoom READ getCurrentRoom NOTIFY currentRoomChanged)
|
||||
Q_PROPERTY(QList<Room*> rooms MEMBER rooms NOTIFY roomListChanged)
|
||||
Q_PROPERTY(QString homeserverURL READ getHomeserverURL NOTIFY homeserverChanged)
|
||||
Q_PROPERTY(MemberModel* memberModel READ getMemberModel NOTIFY currentRoomChanged)
|
||||
Q_PROPERTY(MemberListSortModel* memberModel READ getMemberModel NOTIFY currentRoomChanged)
|
||||
Q_PROPERTY(QString displayName READ getDisplayName NOTIFY displayNameChanged)
|
||||
Q_PROPERTY(QVariantList joinedCommunities READ getJoinedCommunitiesList NOTIFY joinedCommunitiesChanged)
|
||||
Q_PROPERTY(RoomListSortModel* publicRooms READ getDirectoryListModel NOTIFY publicRoomsChanged)
|
||||
|
@ -29,7 +34,10 @@ class MatrixCore : public QObject
|
|||
Q_PROPERTY(bool markdownEnabled READ getMarkdownEnabled WRITE setMarkdownEnabled NOTIFY markdownEnabledChanged)
|
||||
Q_PROPERTY(EmoteListModel* localEmoteModel READ getLocalEmoteListModel NOTIFY localEmotesChanged)
|
||||
public:
|
||||
MatrixCore(QObject* parent = nullptr);
|
||||
MatrixCore(QString profileName, QObject* parent = nullptr);
|
||||
|
||||
Network* network = nullptr;
|
||||
Encryption* encryption = nullptr;
|
||||
|
||||
// account
|
||||
Q_INVOKABLE void registerAccount(const QString& username, const QString& password, const QString& session = "", const QString& type = "");
|
||||
|
@ -83,18 +91,29 @@ public:
|
|||
|
||||
Q_INVOKABLE QString getUsername() const;
|
||||
|
||||
Q_INVOKABLE void loadDirectory();
|
||||
Q_INVOKABLE void loadDirectory(const QString& homeserver);
|
||||
|
||||
Q_INVOKABLE void readUpTo(Room* room, const int index);
|
||||
|
||||
Q_INVOKABLE bool isInitialSyncComplete();
|
||||
|
||||
void setMarkdownEnabled(const bool enabled);
|
||||
|
||||
void sendKeyToDevice(QString roomId, QString senderCurveIdentity, QString senderEdIdentity, QString session_id, QString session_key, QString user_id, QString device_id);
|
||||
|
||||
OlmOutboundGroupSession* currentSession = nullptr;
|
||||
QString currentSessionId, currentSessionKey;
|
||||
QString deviceId;
|
||||
void createOrLoadSession();
|
||||
|
||||
QMap<QString, OlmInboundGroupSession*> inboundSessions;
|
||||
|
||||
Room* getCurrentRoom();
|
||||
|
||||
EventModel* getEventModel();
|
||||
RoomListSortModel* getRoomListModel();
|
||||
RoomListSortModel* getDirectoryListModel();
|
||||
MemberModel* getMemberModel();
|
||||
MemberListSortModel* getMemberModel();
|
||||
EmoteListModel* getLocalEmoteListModel();
|
||||
|
||||
QString getHomeserverURL() const;
|
||||
|
@ -109,10 +128,13 @@ public:
|
|||
RoomListModel roomListModel, directoryListModel;
|
||||
RoomListSortModel roomListSortModel, directoryListSortModel;
|
||||
MemberModel memberModel;
|
||||
MemberListSortModel memberSortModel;
|
||||
EmoteListModel localEmoteModel;
|
||||
|
||||
Room* currentRoom = nullptr;
|
||||
|
||||
QString profileName;
|
||||
|
||||
signals:
|
||||
void registerAttempt(bool error, QString description);
|
||||
void registerFlow(QJsonObject data);
|
||||
|
@ -132,6 +154,7 @@ signals:
|
|||
|
||||
private:
|
||||
void consumeEvent(const QJsonObject& event, Room& room, const bool insertFront = true);
|
||||
void populateEvent(const QJsonObject& event, Event* e);
|
||||
Community* createCommunity(const QString& id);
|
||||
|
||||
QString getMXCThumbnailURL(QString url);
|
||||
|
|
3
include/membermodel.h
Normal file → Executable file
3
include/membermodel.h
Normal file → Executable file
|
@ -11,7 +11,8 @@ public:
|
|||
enum EventRoles {
|
||||
DisplayNameRole = Qt::UserRole + 1,
|
||||
AvatarURLRole,
|
||||
IdRole
|
||||
IdRole,
|
||||
SectionRole
|
||||
};
|
||||
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
|
|
13
include/network.h
Normal file → Executable file
13
include/network.h
Normal file → Executable file
|
@ -8,9 +8,14 @@
|
|||
|
||||
#include "requestsender.h"
|
||||
|
||||
namespace network {
|
||||
extern QNetworkAccessManager* manager;
|
||||
extern QString homeserverURL, accessToken;
|
||||
class Network {
|
||||
public:
|
||||
Network() {
|
||||
manager = new QNetworkAccessManager();
|
||||
}
|
||||
|
||||
QNetworkAccessManager* manager;
|
||||
QString homeserverURL, accessToken;
|
||||
|
||||
template<typename Fn>
|
||||
inline void postJSON(const QString& path, const QJsonObject object, Fn&& fn) {
|
||||
|
@ -120,4 +125,4 @@ namespace network {
|
|||
|
||||
manager->get(request);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
2
include/requestsender.h
Normal file → Executable file
2
include/requestsender.h
Normal file → Executable file
|
@ -23,6 +23,8 @@ public:
|
|||
|
||||
void finished(QNetworkReply* reply) {
|
||||
if(reply->request().originatingObject() == this) {
|
||||
//qDebug() << reply->errorString();
|
||||
|
||||
fn(reply);
|
||||
|
||||
deleteLater();
|
||||
|
|
24
include/room.h
Normal file → Executable file
24
include/room.h
Normal file → Executable file
|
@ -7,6 +7,13 @@
|
|||
|
||||
#include "community.h"
|
||||
|
||||
class EncryptionInformation : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
QString cipherText;
|
||||
QString sessionId;
|
||||
};
|
||||
|
||||
class Event : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -18,10 +25,12 @@ class Event : public QObject
|
|||
Q_PROPERTY(QString thumbnail READ getThumbnail NOTIFY thumbnailChanged)
|
||||
Q_PROPERTY(bool sent READ getSent NOTIFY sentChanged)
|
||||
Q_PROPERTY(double sentProgress READ getSentProgress NOTIFY sentProgressChanged)
|
||||
Q_PROPERTY(QString eventId MEMBER eventId)
|
||||
Q_PROPERTY(QString eventId MEMBER eventId NOTIFY msgChanged)
|
||||
public:
|
||||
Event(QObject* parent = nullptr) : QObject(parent) {}
|
||||
|
||||
EncryptionInformation* encryptionInfo = nullptr;
|
||||
|
||||
void setSender(const QString& id) {
|
||||
sender = id;
|
||||
emit senderChanged();
|
||||
|
@ -203,9 +212,19 @@ class Room : public QObject
|
|||
Q_PROPERTY(QString notificationCount READ getNotificationCount NOTIFY notificationCountChanged)
|
||||
Q_PROPERTY(bool direct READ getDirect NOTIFY directChanged)
|
||||
Q_PROPERTY(int notificationLevel READ getNotificationLevel WRITE setNotificationLevel NOTIFY notificationLevelChanged)
|
||||
Q_PROPERTY(bool encrypted READ getEncrypted NOTIFY encryptionChanged)
|
||||
public:
|
||||
Room(QObject* parent = nullptr) : QObject(parent) {}
|
||||
|
||||
void setEncrypted() {
|
||||
this->encrypted = true;
|
||||
emit encryptionChanged();
|
||||
}
|
||||
|
||||
bool getEncrypted() const {
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
void setId(const QString& id) {
|
||||
this->id = id;
|
||||
emit idChanged();
|
||||
|
@ -309,6 +328,7 @@ public:
|
|||
QString prevBatch;
|
||||
|
||||
QList<Member*> members;
|
||||
QMap<QString, int> powerLevelList;
|
||||
|
||||
private:
|
||||
QString id, name, topic, avatar;
|
||||
|
@ -318,6 +338,7 @@ private:
|
|||
unsigned int highlightCount = 0, notificationCount = 0;
|
||||
bool direct = false;
|
||||
int notificationLevel = 1;
|
||||
bool encrypted = false;
|
||||
|
||||
signals:
|
||||
void idChanged();
|
||||
|
@ -331,4 +352,5 @@ signals:
|
|||
void notificationCountChanged();
|
||||
void directChanged();
|
||||
void notificationLevelChanged();
|
||||
void encryptionChanged();
|
||||
};
|
||||
|
|
17
include/roomlistsortmodel.h
Normal file → Executable file
17
include/roomlistsortmodel.h
Normal file → Executable file
|
@ -19,3 +19,20 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
class MemberListSortModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const;
|
||||
|
||||
Q_INVOKABLE unsigned int getOriginalIndex(const unsigned int i) const {
|
||||
auto const proxyIndex = index(i, 0);
|
||||
auto const sourceIndex = mapToSource(proxyIndex);
|
||||
|
||||
if(!sourceIndex.isValid())
|
||||
return 0;
|
||||
else
|
||||
return sourceIndex.row();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
2
qml.qrc
Normal file → Executable file
2
qml.qrc
Normal file → Executable file
|
@ -10,6 +10,8 @@
|
|||
<file alias="RoomSettings.qml">qml/RoomSettings.qml</file>
|
||||
<file alias="Profile.qml">qml/Profile.qml</file>
|
||||
<file alias="Community.qml">qml/Community.qml</file>
|
||||
<file alias="ToolBarButton.qml">qml/ToolBarButton.qml</file>
|
||||
<file alias="RoundedImage.qml">qml/RoundedImage.qml</file>
|
||||
<file alias="Communities.qml">qml/Communities.qml</file>
|
||||
<file alias="Directory.qml">qml/Directory.qml</file>
|
||||
<file alias="InviteDialog.qml">qml/InviteDialog.qml</file>
|
||||
|
|
2
qml/BackButton.qml
Normal file → Executable file
2
qml/BackButton.qml
Normal file → Executable file
|
@ -27,7 +27,7 @@ Rectangle {
|
|||
|
||||
text: "ESC"
|
||||
|
||||
color: "grey"
|
||||
color: myPalette.text
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
|
|
601
qml/Client.qml
Normal file → Executable file
601
qml/Client.qml
Normal file → Executable file
|
@ -1,22 +1,130 @@
|
|||
import QtQuick 2.10
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.3
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Dialogs 1.2
|
||||
|
||||
import QtQuick.Layouts 1.1
|
||||
import trinity.matrix 1.0
|
||||
|
||||
Rectangle {
|
||||
id: client
|
||||
color: Qt.rgba(0.05, 0.05, 0.05, 1.0)
|
||||
color: myPalette.window
|
||||
|
||||
property bool shouldScroll: false
|
||||
|
||||
ListView {
|
||||
property var openProfile: function(member) {
|
||||
var popup = Qt.createComponent("qrc:/Profile.qml")
|
||||
var popupContainer = popup.createObject(window, {"parent": window, "member": member})
|
||||
|
||||
popupContainer.open()
|
||||
}
|
||||
|
||||
property var openRoom: function(room) {
|
||||
var popup = Qt.createComponent("qrc:/RoomSettings.qml")
|
||||
var popupContainer = popup.createObject(window, {"parent": window, "room": room})
|
||||
|
||||
popupContainer.open()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
visible: !matrix.initialSyncComplete
|
||||
|
||||
z: 1000
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
id: syncIndicator
|
||||
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.top: syncIndicator.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
text: "Synchronizing events..."
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 15
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
text: "Log out"
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: channels
|
||||
|
||||
width: 180
|
||||
height: parent.height
|
||||
anchors.right: rightArea.left
|
||||
anchors.left: client.left
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
Rectangle {
|
||||
id: profileRect
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
height: 45
|
||||
|
||||
color: myPalette.mid
|
||||
|
||||
Text {
|
||||
text: "Placeholder name"
|
||||
}
|
||||
|
||||
Button {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
|
||||
text: "Switch"
|
||||
|
||||
onClicked: accountMenu.popup()
|
||||
|
||||
Menu {
|
||||
id: accountMenu
|
||||
|
||||
Repeater {
|
||||
model: app.accounts
|
||||
|
||||
MenuItem {
|
||||
text: modelData.profileName
|
||||
|
||||
onClicked: app.switchAccount(modelData.profileName)
|
||||
}
|
||||
}
|
||||
|
||||
MenuSeparator {}
|
||||
|
||||
MenuItem {
|
||||
text: "Add new account"
|
||||
|
||||
onClicked: app.addAccount("test")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: channelListView
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: profileRect.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.margins: 10
|
||||
|
||||
spacing: 5
|
||||
|
||||
model: matrix.roomListModel
|
||||
|
||||
|
@ -36,27 +144,26 @@ Rectangle {
|
|||
|
||||
text: section
|
||||
|
||||
color: Qt.rgba(0.8, 0.8, 0.8, 1.0)
|
||||
color: myPalette.text
|
||||
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
width: parent.width
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 25
|
||||
|
||||
property bool selected: channels.currentIndex === matrix.roomListModel.getOriginalIndex(index)
|
||||
property bool selected: channelListView.currentIndex === matrix.roomListModel.getOriginalIndex(index)
|
||||
|
||||
color: selected ? "white" : "transparent"
|
||||
color: selected ? myPalette.highlight : "transparent"
|
||||
|
||||
radius: 5
|
||||
|
||||
Image {
|
||||
RoundedImage {
|
||||
id: roomAvatar
|
||||
|
||||
cache: true
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 5
|
||||
anchors.left: parent.left
|
||||
|
@ -65,24 +172,7 @@ Rectangle {
|
|||
width: 18
|
||||
height: 18
|
||||
|
||||
sourceSize.width: 18
|
||||
sourceSize.height: 18
|
||||
|
||||
source: avatarURL ? avatarURL : "placeholder.png"
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Item {
|
||||
width: roomAvatar.width
|
||||
height: roomAvatar.height
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: roomAvatar.width
|
||||
height: roomAvatar.height
|
||||
radius: Math.min(width, height)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
|
@ -92,10 +182,13 @@ Rectangle {
|
|||
|
||||
anchors.left: roomAvatar.right
|
||||
anchors.leftMargin: 5
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 5
|
||||
|
||||
color: selected ? "black" : (highlightCount > 0 ? "red" : (notificationCount > 0 ? "blue" : "white"))
|
||||
color: selected ? "white" : (highlightCount > 0 ? "red" : (notificationCount > 0 ? "blue" : myPalette.text))
|
||||
|
||||
textFormat: Text.PlainText
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
@ -110,7 +203,7 @@ Rectangle {
|
|||
if(!selected) {
|
||||
var originalIndex = matrix.roomListModel.getOriginalIndex(index)
|
||||
matrix.changeCurrentRoom(originalIndex)
|
||||
channels.currentIndex = originalIndex
|
||||
channelListView.currentIndex = originalIndex
|
||||
}
|
||||
} else
|
||||
contextMenu.popup()
|
||||
|
@ -172,9 +265,9 @@ Rectangle {
|
|||
MenuSeparator {}
|
||||
|
||||
MenuItem {
|
||||
text: "Room Settings"
|
||||
text: "Settings"
|
||||
|
||||
onReleased: stack.push("qrc:/RoomSettings.qml", {"room": matrix.getRoom(matrix.roomListModel.getOriginalIndex(index))})
|
||||
onReleased: openRoom(matrix.getRoom(matrix.roomListModel.getOriginalIndex(index)))
|
||||
}
|
||||
|
||||
MenuSeparator {}
|
||||
|
@ -204,12 +297,22 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
width: parent.width
|
||||
|
||||
anchors.bottom: directoryButton.top
|
||||
|
||||
text: "Switch account"
|
||||
|
||||
onClicked: app.addAccount("test")
|
||||
}
|
||||
|
||||
Button {
|
||||
id: communitiesButton
|
||||
|
||||
width: channels.width
|
||||
width: parent.width
|
||||
|
||||
anchors.bottom: channels.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
text: "Communities"
|
||||
|
||||
|
@ -219,7 +322,7 @@ Rectangle {
|
|||
Button {
|
||||
id: directoryButton
|
||||
|
||||
width: channels.width
|
||||
width: parent.width
|
||||
|
||||
anchors.bottom: communitiesButton.top
|
||||
|
||||
|
@ -227,29 +330,74 @@ Rectangle {
|
|||
|
||||
onClicked: stack.push("qrc:/Directory.qml")
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: rightArea
|
||||
height: parent.height
|
||||
width: parent.width - channels.width
|
||||
anchors.left: channels.right
|
||||
anchors.right: parent.right
|
||||
|
||||
color: "green"
|
||||
Rectangle {
|
||||
id: overlay
|
||||
|
||||
z: 999
|
||||
|
||||
anchors.top: roomHeader.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
visible: matrix.currentRoom.guestDenied
|
||||
|
||||
Text {
|
||||
id: invitedByLabel
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
color: myPalette.text
|
||||
|
||||
text: "You have been invited to this room by " + matrix.currentRoom.invitedBy
|
||||
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.top: invitedByLabel.bottom
|
||||
anchors.topMargin: 15
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Button {
|
||||
text: "Accept"
|
||||
|
||||
onReleased: {
|
||||
matrix.joinRoom(matrix.currentRoom.id)
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Deny"
|
||||
|
||||
onReleased: {
|
||||
matrix.joinRoom(matrix.currentRoom.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: roomHeader
|
||||
height: 45
|
||||
width: parent.width
|
||||
|
||||
anchors.bottom: messagesArea.top
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
color: Qt.rgba(0.3, 0.3, 0.3, 1.0)
|
||||
color: myPalette.mid
|
||||
|
||||
Image {
|
||||
RoundedImage {
|
||||
id: channelAvatar
|
||||
|
||||
cache: true
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 15
|
||||
|
@ -257,32 +405,13 @@ Rectangle {
|
|||
width: 33
|
||||
height: 33
|
||||
|
||||
sourceSize.width: 33
|
||||
sourceSize.height: 33
|
||||
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
source: matrix.currentRoom.avatar ? matrix.currentRoom.avatar : "placeholder.png"
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Item {
|
||||
width: 33
|
||||
height: 33
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: 33
|
||||
height: 33
|
||||
radius: Math.min(width, height)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: channelTitle
|
||||
|
||||
font.pointSize: 15
|
||||
font.pointSize: 12
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: 15
|
||||
|
@ -290,22 +419,31 @@ Rectangle {
|
|||
|
||||
text: matrix.currentRoom.name
|
||||
|
||||
color: "white"
|
||||
color: myPalette.text
|
||||
|
||||
textFormat: Text.PlainText
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onReleased: openRoom(matrix.currentRoom)
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: channelTopic
|
||||
|
||||
width: showMemberListButton.x - x
|
||||
|
||||
font.pointSize: 12
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
maximumLineCount: 1
|
||||
|
||||
anchors.left: channelTitle.right
|
||||
anchors.leftMargin: 5
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: audioCallButton.left
|
||||
|
||||
text: {
|
||||
if(matrix.currentRoom.direct)
|
||||
|
@ -317,7 +455,7 @@ Rectangle {
|
|||
return matrix.currentRoom.topic
|
||||
}
|
||||
|
||||
color: "gray"
|
||||
color: myPalette.text
|
||||
|
||||
elide: Text.ElideRight
|
||||
|
||||
|
@ -332,105 +470,94 @@ Rectangle {
|
|||
textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: showMemberListButton
|
||||
ToolBarButton {
|
||||
id: videoCallButton
|
||||
|
||||
width: 25
|
||||
height: 25
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
anchors.right: audioCallButton.left
|
||||
anchors.rightMargin: 10
|
||||
|
||||
|
||||
name: "Video Call"
|
||||
toolIcon: "icons/memberlist.png"
|
||||
}
|
||||
|
||||
ToolBarButton {
|
||||
id: audioCallButton
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
anchors.right: notificationsButton.left
|
||||
anchors.rightMargin: 10
|
||||
|
||||
name: "Voice Call"
|
||||
toolIcon: "icons/memberlist.png"
|
||||
}
|
||||
|
||||
ToolBarButton {
|
||||
id: notificationsButton
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
anchors.right: roomInfoButton.left
|
||||
anchors.rightMargin: 10
|
||||
|
||||
name: "Notifications"
|
||||
toolIcon: "icons/memberlist.png"
|
||||
}
|
||||
|
||||
ToolBarButton {
|
||||
id: roomInfoButton
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
anchors.right: settingsButton.left
|
||||
anchors.rightMargin: 10
|
||||
|
||||
onClicked: {
|
||||
onPressed: {
|
||||
if(memberList.width == 0)
|
||||
memberList.width = 200
|
||||
else
|
||||
memberList.width = 0
|
||||
}
|
||||
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: "Member List"
|
||||
|
||||
background: Rectangle { color: "transparent" }
|
||||
contentItem: Rectangle { color: "transparent" }
|
||||
|
||||
visible: !matrix.currentRoom.direct
|
||||
|
||||
Image {
|
||||
id: memberListButtonImage
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
sourceSize.width: parent.width
|
||||
sourceSize.height: parent.height
|
||||
|
||||
source: "icons/memberlist.png"
|
||||
name: "Room Info"
|
||||
toolIcon: "icons/memberlist.png"
|
||||
isActivated: memberList.width == 200
|
||||
}
|
||||
|
||||
ColorOverlay {
|
||||
anchors.fill: parent
|
||||
source: memberListButtonImage
|
||||
|
||||
color: parent.hovered ? "white" : (memberList.width == 200 ? "white" : Qt.rgba(0.8, 0.8, 0.8, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
ToolBarButton {
|
||||
id: settingsButton
|
||||
|
||||
width: 25
|
||||
height: 25
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 15
|
||||
|
||||
onClicked: stack.push("qrc:/Settings.qml")
|
||||
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: "Settings"
|
||||
|
||||
background: Rectangle { color: "transparent" }
|
||||
contentItem: Rectangle { color: "transparent" }
|
||||
|
||||
Image {
|
||||
id: settingsButtonImage
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
sourceSize.width: parent.width
|
||||
sourceSize.height: parent.height
|
||||
|
||||
source: "icons/settings.png"
|
||||
}
|
||||
|
||||
ColorOverlay {
|
||||
anchors.fill: parent
|
||||
source: settingsButtonImage
|
||||
|
||||
color: parent.hovered ? "white" : Qt.rgba(0.8, 0.8, 0.8, 1.0)
|
||||
}
|
||||
onPressed: stack.push("qrc:/Settings.qml")
|
||||
name: "Settings"
|
||||
toolIcon: "icons/settings.png"
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: messagesArea
|
||||
|
||||
width: parent.width - memberList.width
|
||||
height: parent.height - roomHeader.height
|
||||
|
||||
anchors.top: roomHeader.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: memberList.left
|
||||
|
||||
Rectangle {
|
||||
height: parent.height - messageInputParent.height
|
||||
width: parent.width
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: messageInputParent.top
|
||||
|
||||
clip: true
|
||||
|
||||
color: Qt.rgba(0.1, 0.1, 0.1, 1.0)
|
||||
color: myPalette.light
|
||||
|
||||
ListView {
|
||||
id: messages
|
||||
|
@ -441,7 +568,10 @@ Rectangle {
|
|||
cacheBuffer: 200
|
||||
|
||||
delegate: Rectangle {
|
||||
width: parent.width
|
||||
anchors.left: messages.contentItem.left
|
||||
anchors.right: messages.contentItem.right
|
||||
anchors.margins: 10
|
||||
|
||||
height: (condense ? 5 : 25) + messageArea.height
|
||||
|
||||
color: "transparent"
|
||||
|
@ -451,47 +581,31 @@ Rectangle {
|
|||
property var eventId: display.eventId
|
||||
property var msg: display.msg
|
||||
|
||||
Image {
|
||||
RoundedImage {
|
||||
id: avatar
|
||||
|
||||
width: 33
|
||||
height: 33
|
||||
|
||||
cache: true
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 5
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 5
|
||||
|
||||
sourceSize.width: 33
|
||||
sourceSize.height: 33
|
||||
|
||||
source: sender.avatarURL ? sender.avatarURL : "placeholder.png"
|
||||
source: sender == null ? "placeholder.png"
|
||||
: (sender.avatarURL ? sender.avatarURL : "placeholder.png")
|
||||
|
||||
visible: !condense
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Item {
|
||||
width: avatar.width
|
||||
height: avatar.height
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: avatar.width
|
||||
height: avatar.height
|
||||
radius: Math.min(width, height)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: senderText
|
||||
|
||||
text: condense ? "" : sender.displayName
|
||||
text: condense || sender == null ? "" : sender.displayName
|
||||
|
||||
color: "white"
|
||||
color: myPalette.text
|
||||
|
||||
font.bold: true
|
||||
|
||||
anchors.left: avatar.right
|
||||
anchors.leftMargin: 10
|
||||
|
@ -499,10 +613,23 @@ Rectangle {
|
|||
textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: senderClickArea
|
||||
|
||||
anchors.left: avatar.left
|
||||
anchors.right: senderText.right
|
||||
anchors.top: avatar.top
|
||||
anchors.bottom: avatar.bottom
|
||||
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: openProfile(sender)
|
||||
}
|
||||
|
||||
Text {
|
||||
text: condense ? "" : timestamp
|
||||
|
||||
color: "gray"
|
||||
color: myPalette.dark
|
||||
|
||||
anchors.left: senderText.right
|
||||
anchors.leftMargin: 10
|
||||
|
@ -524,27 +651,27 @@ Rectangle {
|
|||
return preview.height
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
|
||||
anchors.left: condense ? parent.left : avatar.right
|
||||
anchors.leftMargin: condense ? 48 : 10
|
||||
|
||||
anchors.right: parent.right
|
||||
|
||||
color: "transparent"
|
||||
|
||||
TextEdit {
|
||||
id: message
|
||||
|
||||
text: display.msg
|
||||
|
||||
width: parent.width
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
text: display.msg
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
textFormat: Text.RichText
|
||||
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
|
||||
color: display.sent ? "white" : "gray"
|
||||
color: display.sent ? myPalette.text : myPalette.mid
|
||||
|
||||
visible: display.msgType === "text"
|
||||
}
|
||||
|
@ -696,35 +823,10 @@ Rectangle {
|
|||
onContentHeightChanged: {
|
||||
var index = indexAt(0, contentY + height - 5)
|
||||
matrix.readUpTo(matrix.currentRoom, index)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: overlay
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
visible: matrix.currentRoom.guestDenied
|
||||
|
||||
Text {
|
||||
id: invitedByLabel
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
color: "white"
|
||||
|
||||
text: "You have been invited to this room by " + matrix.currentRoom.invitedBy
|
||||
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Accept"
|
||||
|
||||
anchors.top: invitedByLabel.bottom
|
||||
|
||||
onReleased: {
|
||||
matrix.joinRoom(matrix.currentRoom.id)
|
||||
//print(contentHeight + "," + height);
|
||||
if(contentHeight < height) {
|
||||
matrix.readMessageHistory(matrix.currentRoom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -733,12 +835,12 @@ Rectangle {
|
|||
Rectangle {
|
||||
id: messageInputParent
|
||||
|
||||
anchors.top: messages.parent.bottom
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
width: parent.width
|
||||
height: 55
|
||||
|
||||
color: Qt.rgba(0.1, 0.1, 0.1, 1.0)
|
||||
color: "transparent"
|
||||
|
||||
ToolButton {
|
||||
id: attachButton
|
||||
|
@ -775,7 +877,7 @@ Rectangle {
|
|||
anchors.right: parent.right
|
||||
anchors.rightMargin: 5
|
||||
|
||||
placeholderText: "Message " + matrix.currentRoom.name
|
||||
placeholderText: "Send a " + (matrix.currentRoom.encrypted ? "encrypted" : "unencrypted") + " message to " + matrix.currentRoom.name
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
if(event.modifiers & Qt.ShiftModifier) {
|
||||
|
@ -836,7 +938,7 @@ Rectangle {
|
|||
|
||||
anchors.bottom: messageInputParent.bottom
|
||||
|
||||
color: "white"
|
||||
color: myPalette.text
|
||||
|
||||
text: matrix.typingText
|
||||
|
||||
|
@ -849,53 +951,66 @@ Rectangle {
|
|||
id: memberList
|
||||
|
||||
anchors.top: roomHeader.bottom
|
||||
anchors.left: messagesArea.right
|
||||
anchors.right: parent.right
|
||||
|
||||
color: Qt.rgba(0.15, 0.15, 0.15, 1.0)
|
||||
color: myPalette.midlight
|
||||
|
||||
width: matrix.currentRoom.direct ? 0 : 200
|
||||
height: parent.height - roomHeader.height
|
||||
|
||||
ListView {
|
||||
id: memberListView
|
||||
|
||||
model: matrix.memberModel
|
||||
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: inviteButton.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
section.property: "section"
|
||||
section.criteria: ViewSection.FullString
|
||||
section.delegate: Rectangle {
|
||||
width: parent.width
|
||||
height: 25
|
||||
|
||||
color: "transparent"
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 5
|
||||
|
||||
text: section
|
||||
|
||||
color: myPalette.text
|
||||
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
width: parent.width
|
||||
width: memberListView.contentItem.width
|
||||
height: 50
|
||||
|
||||
color: "transparent"
|
||||
|
||||
property string memberId: id
|
||||
|
||||
Image {
|
||||
RoundedImage {
|
||||
id: memberAvatar
|
||||
|
||||
cache: true
|
||||
width: 33
|
||||
height: 33
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
|
||||
sourceSize.width: 33
|
||||
sourceSize.height: 33
|
||||
|
||||
source: avatarURL ? avatarURL : "placeholder.png"
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Item {
|
||||
width: memberAvatar.width
|
||||
height: memberAvatar.height
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: memberAvatar.width
|
||||
height: memberAvatar.height
|
||||
radius: Math.min(width, height)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
|
@ -904,7 +1019,7 @@ Rectangle {
|
|||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
color: "white"
|
||||
color: myPalette.text
|
||||
|
||||
text: displayName
|
||||
|
||||
|
@ -914,9 +1029,17 @@ Rectangle {
|
|||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
acceptedButtons: Qt.RightButton
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
|
||||
onClicked: memberContextMenu.popup()
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: function(mouse) {
|
||||
if(mouse.button == Qt.LeftButton) {
|
||||
openProfile(matrix.resolveMemberId(id))
|
||||
} else {
|
||||
memberContextMenu.popup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Menu {
|
||||
|
@ -925,12 +1048,7 @@ Rectangle {
|
|||
MenuItem {
|
||||
text: "Profile"
|
||||
|
||||
onReleased: {
|
||||
var popup = Qt.createComponent("qrc:/Profile.qml")
|
||||
var popupContainer = popup.createObject(client, {"parent": client, "member": matrix.resolveMemberId(id)})
|
||||
|
||||
popupContainer.open()
|
||||
}
|
||||
onReleased: openProfile(matrix.resolveMemberId(id))
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
|
@ -970,15 +1088,14 @@ Rectangle {
|
|||
boundsBehavior: Flickable.StopAtBounds
|
||||
flickableDirection: Flickable.VerticalFlick
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: inviteButton
|
||||
|
||||
width: memberList.width
|
||||
|
||||
anchors.bottom: memberList.bottom
|
||||
anchors.right: memberList.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 8
|
||||
|
||||
text: "Invite to room"
|
||||
|
||||
|
@ -990,6 +1107,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: syncTimer
|
||||
|
@ -1021,21 +1139,24 @@ Rectangle {
|
|||
|
||||
Connections {
|
||||
target: matrix
|
||||
onSyncFinished: {
|
||||
|
||||
function onSyncFinished() {
|
||||
syncTimer.start()
|
||||
|
||||
if(shouldScroll)
|
||||
messages.positionViewAtEnd()
|
||||
}
|
||||
|
||||
onInitialSyncFinished: matrix.changeCurrentRoom(0)
|
||||
function onInitialSyncFinished() {
|
||||
matrix.changeCurrentRoom(0)
|
||||
}
|
||||
|
||||
onCurrentRoomChanged: {
|
||||
function onCurrentRoomChanged() {
|
||||
if(messages.contentY < messages.originY + 5)
|
||||
matrix.readMessageHistory(matrix.currentRoom)
|
||||
}
|
||||
|
||||
onMessage: {
|
||||
function onMessage(room, sender, content) {
|
||||
var notificationLevel = room.notificationLevel
|
||||
var shouldDisplay = false
|
||||
|
||||
|
|
4
qml/Dialog.qml
Normal file → Executable file
4
qml/Dialog.qml
Normal file → Executable file
|
@ -22,7 +22,7 @@ Popup {
|
|||
|
||||
text: title
|
||||
|
||||
color: "white"
|
||||
color: myPalette.text
|
||||
}
|
||||
|
||||
Text {
|
||||
|
@ -32,7 +32,7 @@ Popup {
|
|||
|
||||
anchors.top: titleLabel.bottom
|
||||
|
||||
color: "white"
|
||||
color: myPalette.text
|
||||
}
|
||||
|
||||
Repeater {
|
||||
|
|
25
qml/Directory.qml
Normal file → Executable file
25
qml/Directory.qml
Normal file → Executable file
|
@ -8,9 +8,7 @@ import trinity.matrix 1.0
|
|||
Rectangle {
|
||||
id: roomDirectory
|
||||
|
||||
color: Qt.rgba(0.1, 0.1, 0.1, 1.0)
|
||||
|
||||
Component.onCompleted: matrix.loadDirectory()
|
||||
color: myPalette.window
|
||||
|
||||
Rectangle {
|
||||
width: 700
|
||||
|
@ -40,14 +38,25 @@ Rectangle {
|
|||
font.pointSize: 25
|
||||
font.bold: true
|
||||
|
||||
color: "white"
|
||||
color: myPalette.text
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
id: serverEdit
|
||||
|
||||
anchors.top: directoryLabel.bottom
|
||||
anchors.topMargin: 10
|
||||
|
||||
width: parent.width
|
||||
|
||||
onEditingFinished: matrix.loadDirectory(text)
|
||||
}
|
||||
|
||||
ListView {
|
||||
width: parent.width
|
||||
height: parent.height - backButton.height
|
||||
|
||||
anchors.top: directoryLabel.bottom
|
||||
anchors.top: serverEdit.bottom
|
||||
anchors.topMargin: 10
|
||||
|
||||
model: matrix.publicRooms
|
||||
|
@ -60,7 +69,7 @@ Rectangle {
|
|||
|
||||
color: "transparent"
|
||||
|
||||
Image {
|
||||
RoundedImage {
|
||||
id: roomAvatar
|
||||
|
||||
width: 32
|
||||
|
@ -81,7 +90,7 @@ Rectangle {
|
|||
|
||||
font.bold: true
|
||||
|
||||
color: "white"
|
||||
color: myPalette.text
|
||||
}
|
||||
|
||||
Text {
|
||||
|
@ -98,7 +107,7 @@ Rectangle {
|
|||
|
||||
wrapMode: Text.Wrap
|
||||
|
||||
color: "white"
|
||||
color: myPalette.text
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
|
61
qml/Profile.qml
Normal file → Executable file
61
qml/Profile.qml
Normal file → Executable file
|
@ -18,7 +18,7 @@ Popup {
|
|||
|
||||
Component.onCompleted: matrix.updateMemberCommunities(member)
|
||||
|
||||
Image {
|
||||
RoundedImage {
|
||||
id: profileAvatar
|
||||
|
||||
width: 64
|
||||
|
@ -38,7 +38,7 @@ Popup {
|
|||
|
||||
font.pointSize: 22
|
||||
|
||||
color: "white"
|
||||
color: myPalette.text
|
||||
}
|
||||
|
||||
Text {
|
||||
|
@ -50,7 +50,40 @@ Popup {
|
|||
|
||||
text: member.id
|
||||
|
||||
color: "grey"
|
||||
color: myPalette.dark
|
||||
}
|
||||
|
||||
Text {
|
||||
id: roleText
|
||||
|
||||
anchors.top: profileIdLabel.bottom
|
||||
anchors.topMargin: 5
|
||||
anchors.left: profileAvatar.right
|
||||
anchors.leftMargin: 15
|
||||
|
||||
text: "Member Role Placeholder"
|
||||
|
||||
color: myPalette.dark
|
||||
}
|
||||
|
||||
Button {
|
||||
id: directMessageButton
|
||||
|
||||
anchors.verticalCenter: profileAvatar.verticalCenter
|
||||
anchors.right: hamburgerButton.left
|
||||
anchors.rightMargin: 10
|
||||
|
||||
text: "Direct message"
|
||||
}
|
||||
|
||||
Button {
|
||||
id: hamburgerButton
|
||||
|
||||
anchors.verticalCenter: profileAvatar.verticalCenter
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 15
|
||||
|
||||
text: "..."
|
||||
}
|
||||
|
||||
TabBar {
|
||||
|
@ -61,12 +94,22 @@ Popup {
|
|||
|
||||
id: profileTabs
|
||||
|
||||
TabButton {
|
||||
text: "Security"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "Communities"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "Sessions"
|
||||
}
|
||||
}
|
||||
|
||||
SwipeView {
|
||||
interactive: false
|
||||
|
||||
height: parent.height - profileNameLabel.height - profileTabs.height
|
||||
width: parent.width
|
||||
|
||||
|
@ -75,6 +118,8 @@ Popup {
|
|||
currentIndex: profileTabs.currentIndex
|
||||
|
||||
Item {
|
||||
id: communityTab
|
||||
|
||||
ListView {
|
||||
id: communityList
|
||||
|
||||
|
@ -90,7 +135,7 @@ Popup {
|
|||
|
||||
color: "transparent"
|
||||
|
||||
Image {
|
||||
RoundedImage {
|
||||
id: communityAvatar
|
||||
|
||||
width: 32
|
||||
|
@ -110,7 +155,7 @@ Popup {
|
|||
|
||||
text: display.name
|
||||
|
||||
color: "white"
|
||||
color: myPalette.text
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
@ -135,11 +180,15 @@ Popup {
|
|||
|
||||
text: "This member does not have any public communities."
|
||||
|
||||
color: "white"
|
||||
color: myPalette.text
|
||||
|
||||
visible: !member.publicCommunities || member.publicCommunities.length == 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: sessionsTab
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
41
qml/RoomSettings.qml
Normal file → Executable file
41
qml/RoomSettings.qml
Normal file → Executable file
|
@ -3,35 +3,48 @@ import QtQuick.Controls 2.3
|
|||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Shapes 1.0
|
||||
|
||||
Rectangle {
|
||||
Popup {
|
||||
id: roomSettings
|
||||
|
||||
color: Qt.rgba(0.1, 0.1, 0.1, 1.0)
|
||||
width: 500
|
||||
height: 256
|
||||
|
||||
x: parent.width / 2 - width / 2
|
||||
y: parent.height / 2 - height / 2
|
||||
|
||||
modal: true
|
||||
|
||||
property var room
|
||||
|
||||
Rectangle {
|
||||
width: 700
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
color: "transparent"
|
||||
|
||||
Button {
|
||||
id: backButton
|
||||
|
||||
text: "Back"
|
||||
onClicked: stack.pop()
|
||||
}
|
||||
|
||||
TabBar {
|
||||
id: bar
|
||||
|
||||
anchors.top: backButton.bottom
|
||||
TabButton {
|
||||
text: "General"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "Overview"
|
||||
text: "Security & Privacy"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "Roles & Permissions"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "Notifications"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "Advanced"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,7 +66,7 @@ Rectangle {
|
|||
Label {
|
||||
id: nameLabel
|
||||
|
||||
text: "Name"
|
||||
text: "Room Name"
|
||||
}
|
||||
|
||||
TextField {
|
||||
|
@ -67,7 +80,7 @@ Rectangle {
|
|||
Label {
|
||||
id: topicLabel
|
||||
|
||||
text: "Topic"
|
||||
text: "Room Topic"
|
||||
|
||||
anchors.top: nameField.bottom
|
||||
}
|
||||
|
|
36
qml/RoundedImage.qml
Executable file
36
qml/RoundedImage.qml
Executable file
|
@ -0,0 +1,36 @@
|
|||
import QtQuick 2.15
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
Item {
|
||||
property var source: String
|
||||
|
||||
Image {
|
||||
id: image
|
||||
|
||||
cache: true
|
||||
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
sourceSize.width: parent.width
|
||||
sourceSize.height: parent.height
|
||||
|
||||
source: parent.source
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: OpacityMask {
|
||||
maskSource: Item {
|
||||
width: image.width
|
||||
height: image.height
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: image.width
|
||||
height: image.height
|
||||
radius: Math.min(width, height)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
32
qml/Settings.qml
Normal file → Executable file
32
qml/Settings.qml
Normal file → Executable file
|
@ -3,11 +3,12 @@ import QtQuick.Controls 2.3
|
|||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Shapes 1.0
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
Rectangle {
|
||||
id: settings
|
||||
|
||||
color: Qt.rgba(0.1, 0.1, 0.1, 1.0)
|
||||
color: myPalette.window
|
||||
|
||||
Component.onCompleted: matrix.updateAccountInformation()
|
||||
|
||||
|
@ -19,11 +20,13 @@ Rectangle {
|
|||
|
||||
color: "transparent"
|
||||
|
||||
Button {
|
||||
BackButton {
|
||||
id: backButton
|
||||
|
||||
text: "Back"
|
||||
onClicked: stack.pop()
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 15
|
||||
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
TabBar {
|
||||
|
@ -50,7 +53,7 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
SwipeView {
|
||||
StackLayout {
|
||||
id: settingsStack
|
||||
|
||||
anchors.top: bar.bottom
|
||||
|
@ -155,12 +158,27 @@ Rectangle {
|
|||
id: notificationsTab
|
||||
|
||||
CheckBox {
|
||||
text: "Enable Desktop Notifications"
|
||||
id: notificationsEnable
|
||||
|
||||
text: "Enable notifications"
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
text: "Enable notification bar icon"
|
||||
|
||||
anchors.top: notificationsEnable.bottom
|
||||
|
||||
checked: desktop.isTrayIconEnabled()
|
||||
onToggled: desktop.showTrayIcon(checked)
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: appearanceTab
|
||||
|
||||
CheckBox {
|
||||
text: "Show developer options"
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
|
@ -220,7 +238,7 @@ Rectangle {
|
|||
|
||||
text: display.name
|
||||
|
||||
color: "white"
|
||||
color: myPalette.text
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
|
|
42
qml/ToolBarButton.qml
Executable file
42
qml/ToolBarButton.qml
Executable file
|
@ -0,0 +1,42 @@
|
|||
import QtQuick 2.15
|
||||
import QtGraphicalEffects 1.0
|
||||
import QtQuick.Controls 2.3
|
||||
|
||||
ToolButton {
|
||||
width: 25
|
||||
height: 25
|
||||
|
||||
signal pressed()
|
||||
|
||||
property var name: String
|
||||
property var toolIcon: String
|
||||
property bool isActivated: false
|
||||
|
||||
onClicked: pressed()
|
||||
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: name
|
||||
|
||||
background: Rectangle { color: "transparent" }
|
||||
contentItem: Rectangle { color: "transparent" }
|
||||
|
||||
visible: !matrix.currentRoom.direct
|
||||
|
||||
Image {
|
||||
id: internalImage
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
sourceSize.width: parent.width
|
||||
sourceSize.height: parent.height
|
||||
|
||||
source: toolIcon
|
||||
}
|
||||
|
||||
ColorOverlay {
|
||||
anchors.fill: parent
|
||||
source: internalImage
|
||||
|
||||
color: parent.hovered ? "white" : (isActivated ? "white" : Qt.rgba(0.8, 0.8, 0.8, 1.0))
|
||||
}
|
||||
}
|
51
qml/main.qml
Normal file → Executable file
51
qml/main.qml
Normal file → Executable file
|
@ -9,7 +9,9 @@ ApplicationWindow {
|
|||
visible: true
|
||||
width: 640
|
||||
height: 480
|
||||
title: "Trinity"
|
||||
title: "Trinity " + matrix.profileName
|
||||
|
||||
SystemPalette { id: myPalette; colorGroup: SystemPalette.Active }
|
||||
|
||||
property var showDialog: function(title, description, buttons) {
|
||||
var popup = Qt.createComponent("qrc:/Dialog.qml")
|
||||
|
@ -24,17 +26,60 @@ ApplicationWindow {
|
|||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
connect.onAccountChange()
|
||||
}
|
||||
|
||||
Connections {
|
||||
id: connect
|
||||
|
||||
target: app
|
||||
|
||||
function onAccountChange() {
|
||||
if(matrix.settingsValid()) {
|
||||
desktop.showTrayIcon(false)
|
||||
stack.push("qrc:/Client.qml")
|
||||
stack.replace("qrc:/Client.qml")
|
||||
} else {
|
||||
desktop.showTrayIcon(true)
|
||||
stack.push("qrc:/Login.qml")
|
||||
stack.replace("qrc:/Login.qml")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StackView {
|
||||
id: stack
|
||||
anchors.fill: parent
|
||||
|
||||
pushEnter: Transition {
|
||||
PropertyAnimation {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to:1
|
||||
duration: 200
|
||||
}
|
||||
}
|
||||
pushExit: Transition {
|
||||
PropertyAnimation {
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to:0
|
||||
duration: 200
|
||||
}
|
||||
}
|
||||
popEnter: Transition {
|
||||
PropertyAnimation {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to:1
|
||||
duration: 200
|
||||
}
|
||||
}
|
||||
popExit: Transition {
|
||||
PropertyAnimation {
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to:0
|
||||
duration: 200
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
337
src/encryption.cpp
Executable file
337
src/encryption.cpp
Executable file
|
@ -0,0 +1,337 @@
|
|||
#include "encryption.h"
|
||||
#include <stdlib.h>
|
||||
#include <QDebug>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <QJsonDocument>
|
||||
#include <cstring>
|
||||
|
||||
void Encryption::createNewDeviceKeys() {
|
||||
initAccount();
|
||||
|
||||
// create account
|
||||
auto length = olm_create_account_random_length(account);
|
||||
|
||||
void* memory = malloc(length);
|
||||
|
||||
int fd = open("/dev/random", O_RDONLY);
|
||||
read(fd, memory, length);
|
||||
|
||||
olm_create_account(account, memory, length);
|
||||
qDebug() << olm_account_last_error(account);
|
||||
|
||||
qDebug() << "Created new device keys!";
|
||||
|
||||
// retrieve identity keys
|
||||
length = olm_account_identity_keys_length(account);
|
||||
|
||||
char* identity_memory = (char*)malloc(length + 1);
|
||||
identity_memory[length] = '\0';
|
||||
|
||||
olm_account_identity_keys(account, identity_memory, length);
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson((char*)identity_memory);
|
||||
identityKey = doc.object();
|
||||
|
||||
qDebug() << "identity keys: " << (char*)identity_memory;
|
||||
qDebug() << "identity keys (parsed): " << identityKey;
|
||||
|
||||
}
|
||||
|
||||
QString Encryption::saveDeviceKeys() {
|
||||
auto length = olm_pickle_account_length(account);
|
||||
char* identity_memory = (char*)malloc(length + 1);
|
||||
identity_memory[length] = '\0';
|
||||
|
||||
auto err = olm_pickle_account(account, "secret_key", 10, identity_memory, length);
|
||||
qDebug() << olm_account_last_error(account);
|
||||
qDebug() << err << " = " << olm_error();
|
||||
|
||||
qDebug() << "Save: " << identity_memory;
|
||||
|
||||
return identity_memory;
|
||||
}
|
||||
|
||||
void Encryption::loadDeviceKeys(QString bytes) {
|
||||
initAccount();
|
||||
|
||||
qDebug() << "load: " << bytes;
|
||||
|
||||
auto length = bytes.length();
|
||||
|
||||
std::string key = bytes.toStdString();
|
||||
|
||||
olm_unpickle_account(account, "secret_key", 10, key.data(), key.length());
|
||||
qDebug() << olm_account_last_error(account);
|
||||
|
||||
length = olm_account_identity_keys_length(account);
|
||||
char* identity_memory = (char*)malloc(length + 1);
|
||||
identity_memory[length] = '\0';
|
||||
|
||||
olm_account_identity_keys(account, identity_memory, length);
|
||||
|
||||
qDebug() << identity_memory;
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(identity_memory);
|
||||
qDebug() << doc;
|
||||
|
||||
identityKey = doc.object();
|
||||
}
|
||||
|
||||
QJsonObject Encryption::generateOneTimeKeys(int number_of_keys) {
|
||||
int fd = open("/dev/random", O_RDONLY);
|
||||
|
||||
// generate random data
|
||||
auto length = olm_account_generate_one_time_keys_random_length(account, number_of_keys);
|
||||
auto memory = malloc(length);
|
||||
read(fd, memory, length);
|
||||
|
||||
olm_account_generate_one_time_keys(account, number_of_keys, memory, length);
|
||||
|
||||
// retrieve one time keys
|
||||
length = olm_account_one_time_keys_length(account);
|
||||
memory = malloc(length);
|
||||
olm_account_one_time_keys(account, memory, length);
|
||||
|
||||
olm_account_mark_keys_as_published(account);
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson((char*)memory);
|
||||
return doc.object();
|
||||
}
|
||||
|
||||
int Encryption::getRecommendedNumberOfOneTimeKeys() {
|
||||
return olm_account_max_number_of_one_time_keys(account) / 2;
|
||||
}
|
||||
|
||||
QString Encryption::signMessage(QString message) {
|
||||
auto length = olm_account_signature_length(account);
|
||||
|
||||
void* memory = malloc(length);
|
||||
|
||||
olm_account_sign(
|
||||
account,
|
||||
message.data(), message.length(),
|
||||
memory, length
|
||||
);
|
||||
|
||||
qDebug() << "Signed: " << (char*)memory;
|
||||
|
||||
return (char*)memory;
|
||||
}
|
||||
|
||||
void Encryption::initAccount() {
|
||||
void* memory = malloc(olm_account_size());
|
||||
account = olm_account(memory);
|
||||
}
|
||||
|
||||
OlmOutboundGroupSession* Encryption::beginOutboundSession() {
|
||||
void* outboundMemory = malloc(olm_outbound_group_session_size());
|
||||
auto outboundSession = olm_outbound_group_session(outboundMemory);
|
||||
|
||||
auto length = olm_init_outbound_group_session_random_length(outboundSession);
|
||||
|
||||
void* memory = malloc(length);
|
||||
|
||||
int fd = open("/dev/random", O_RDONLY);
|
||||
read(fd, memory, length);
|
||||
|
||||
olm_init_outbound_group_session(outboundSession, (uint8_t*)memory, length);
|
||||
|
||||
return outboundSession;
|
||||
}
|
||||
|
||||
std::string Encryption::getGroupSessionId(OlmOutboundGroupSession* session) {
|
||||
auto encryptedLength = olm_outbound_group_session_id_length(session);
|
||||
char* encryptedMemory = (char*)malloc(encryptedLength + 1);
|
||||
|
||||
olm_outbound_group_session_id(session, (uint8_t*)encryptedMemory, encryptedLength);
|
||||
|
||||
return encryptedMemory;
|
||||
}
|
||||
|
||||
std::string Encryption::getGroupSessionKey(OlmOutboundGroupSession* session) {
|
||||
auto encryptedLength = olm_outbound_group_session_key_length(session);
|
||||
char* encryptedMemory = (char*)malloc(encryptedLength + 1);
|
||||
|
||||
olm_outbound_group_session_key(session, (uint8_t*)encryptedMemory, encryptedLength);
|
||||
|
||||
return encryptedMemory;
|
||||
}
|
||||
|
||||
OlmSession* Encryption::beginOutboundOlmSession(std::string identityKey, std::string oneTimeKey) {
|
||||
void* outboundMemory = malloc(olm_session_size());
|
||||
auto outboundSession = olm_session(outboundMemory);
|
||||
|
||||
auto length = olm_create_outbound_session_random_length(outboundSession);
|
||||
|
||||
void* memory = malloc(length);
|
||||
|
||||
int fd = open("/dev/random", O_RDONLY);
|
||||
read(fd, memory, length);
|
||||
|
||||
olm_create_outbound_session(outboundSession, account, identityKey.data(), identityKey.length(), oneTimeKey.data(), oneTimeKey.length(), (uint8_t*)memory, length);
|
||||
//qDebug() << "ERR: " << olm_session_last_error(outboundSession);
|
||||
|
||||
return outboundSession;
|
||||
|
||||
/*size_t olm_create_outbound_session(
|
||||
OlmSession * session,
|
||||
OlmAccount * account,
|
||||
void const * their_identity_key, size_t their_identity_key_length,
|
||||
void const * their_one_time_key, size_t their_one_time_key_length,
|
||||
void * random, size_t random_length
|
||||
);*/
|
||||
|
||||
}
|
||||
|
||||
std::string Encryption::getSessionId(OlmSession* session) {
|
||||
auto length = olm_session_id_length(session);
|
||||
char* memory = (char*)malloc(length + 1);
|
||||
memory[length] = '\0';
|
||||
|
||||
/** An identifier for this session. Will be the same for both ends of the
|
||||
* conversation. If the id buffer is too small then olm_session_last_error()
|
||||
* will be "OUTPUT_BUFFER_TOO_SMALL". */
|
||||
olm_session_id(
|
||||
session,
|
||||
memory, length
|
||||
);
|
||||
|
||||
return memory;
|
||||
}
|
||||
|
||||
std::string Encryption::encrypt(OlmSession* session, std::string message) {
|
||||
auto length = olm_encrypt_random_length(session);
|
||||
|
||||
void* memory = malloc(length);
|
||||
|
||||
int fd = open("/dev/random", O_RDONLY);
|
||||
read(fd, memory, length);
|
||||
|
||||
auto encryptedLength = olm_encrypt_message_length(session, message.length());
|
||||
char* encryptedMemory = (char*)malloc(encryptedLength + 1);
|
||||
|
||||
olm_encrypt(session, message.data(), message.length(), memory, length, encryptedMemory, encryptedLength);
|
||||
|
||||
return encryptedMemory;
|
||||
}
|
||||
|
||||
std::string Encryption::encryptGroup(OlmOutboundGroupSession* session, std::string message) {
|
||||
auto encryptedLength = olm_group_encrypt_message_length(session, message.length());
|
||||
char* encryptedMemory = (char*)malloc(encryptedLength + 1);
|
||||
|
||||
olm_group_encrypt(session, (uint8_t*)message.data(), message.length(), (uint8_t*)encryptedMemory, encryptedLength);
|
||||
|
||||
return encryptedMemory;
|
||||
}
|
||||
|
||||
OlmSession* Encryption::createInboundSession(std::string senderKey, std::string body) {
|
||||
void* inboundMemory = malloc(olm_session_size());
|
||||
auto inboundSession = olm_session(inboundMemory);
|
||||
|
||||
olm_create_inbound_session_from(inboundSession, account, (void*)senderKey.c_str(), senderKey.length(), (void*)body.c_str(), body.length());
|
||||
|
||||
return inboundSession;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> Encryption::decrypt(OlmSession* session, int msgType, std::string cipherText) {
|
||||
/*std::string maxPlaintextLengthBuffer = cipherText;
|
||||
|
||||
size_t maxPlaintextLength = olm_decrypt_max_plaintext_length(session, msgType, (void*)maxPlaintextLengthBuffer.data(), maxPlaintextLengthBuffer.length());;
|
||||
qDebug() << "THE ERROR YOU ARE LOOKING FOR " << olm_session_last_error(session);
|
||||
if(maxPlaintextLength == olm_error())
|
||||
return "";
|
||||
|
||||
char* plaintext = new char[maxPlaintextLength];
|
||||
//plaintext[maxPlaintextLength] = '\0';
|
||||
memset(plaintext, '\0', maxPlaintextLength);
|
||||
int size = olm_decrypt(session, msgType, (void*)cipherText.data(), cipherText.length(), (void*)plaintext, maxPlaintextLength);
|
||||
//plaintext[size] = '\0';
|
||||
|
||||
plaintext[size + 1] = '\0';
|
||||
|
||||
return plaintext;*/
|
||||
|
||||
// because olm_group_decrypt_max_plaintext_length DESTROYS the buffer, why?
|
||||
auto tmp_plaintext_buffer = std::vector<std::uint8_t>(cipherText.size());
|
||||
std::copy(cipherText.begin(), cipherText.end(), tmp_plaintext_buffer.begin());
|
||||
|
||||
// create the result buffer
|
||||
size_t length = olm_decrypt_max_plaintext_length(session, msgType, tmp_plaintext_buffer.data(), tmp_plaintext_buffer.size());
|
||||
if(length == olm_error())
|
||||
return {};
|
||||
|
||||
auto result_buffer = std::vector<std::uint8_t>(length);
|
||||
|
||||
// now create another buffer for olm_group_decrypt
|
||||
auto tmp_buffer = std::vector<std::uint8_t>(cipherText.size());
|
||||
std::copy(cipherText.begin(), cipherText.end(), tmp_buffer.begin());
|
||||
|
||||
auto size = olm_decrypt(session, msgType, tmp_buffer.data(), tmp_buffer.size(), result_buffer.data(), result_buffer.size());
|
||||
if(size == olm_error())
|
||||
return {};
|
||||
|
||||
auto output = std::vector<std::uint8_t>(size);
|
||||
std::memcpy(output.data(), result_buffer.data(), size);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
QString Encryption::saveSession(OlmOutboundGroupSession* session) {
|
||||
auto length = olm_pickle_outbound_group_session_length(session);
|
||||
char* identity_memory = (char*)malloc(length + 1);
|
||||
identity_memory[length] = '\0';
|
||||
|
||||
auto err = olm_pickle_outbound_group_session(session, "secret_key", 10, identity_memory, length);
|
||||
|
||||
return identity_memory;
|
||||
}
|
||||
|
||||
OlmOutboundGroupSession* Encryption::loadSession(QString bytes) {
|
||||
void* outboundMemory = malloc(olm_outbound_group_session_size());
|
||||
auto outboundSession = olm_outbound_group_session(outboundMemory);
|
||||
|
||||
auto length = bytes.length();
|
||||
|
||||
std::string key = bytes.toStdString();
|
||||
|
||||
olm_unpickle_outbound_group_session(outboundSession, "secret_key", 10, (void*)key.data(), key.length());
|
||||
|
||||
return outboundSession;
|
||||
}
|
||||
|
||||
OlmInboundGroupSession* Encryption::beginInboundSession(std::string sessionKey) {
|
||||
void* inboundMemory = malloc(olm_inbound_group_session_size());
|
||||
auto inboundSession = olm_inbound_group_session(inboundMemory);
|
||||
|
||||
olm_init_inbound_group_session(inboundSession, (uint8_t*)sessionKey.data(), sessionKey.size());
|
||||
|
||||
return inboundSession;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> Encryption::decrypt(OlmInboundGroupSession* session, std::string cipherText) {
|
||||
// because olm_group_decrypt_max_plaintext_length DESTROYS the buffer, why?
|
||||
auto tmp_plaintext_buffer = std::vector<std::uint8_t>(cipherText.size());
|
||||
std::copy(cipherText.begin(), cipherText.end(), tmp_plaintext_buffer.begin());
|
||||
|
||||
// create the result buffer
|
||||
size_t length = olm_group_decrypt_max_plaintext_length(session, tmp_plaintext_buffer.data(), tmp_plaintext_buffer.size());
|
||||
if(length == olm_error())
|
||||
return {};
|
||||
|
||||
auto result_buffer = std::vector<std::uint8_t>(length);
|
||||
|
||||
// now create another buffer for olm_group_decrypt
|
||||
auto tmp_buffer = std::vector<std::uint8_t>(cipherText.size());
|
||||
std::copy(cipherText.begin(), cipherText.end(), tmp_buffer.begin());
|
||||
|
||||
uint32_t msgIndex;
|
||||
auto size = olm_group_decrypt(session, tmp_buffer.data(), tmp_buffer.size(), result_buffer.data(), result_buffer.size(), &msgIndex);
|
||||
if(size == olm_error())
|
||||
return {};
|
||||
|
||||
auto output = std::vector<std::uint8_t>(size);
|
||||
std::memcpy(output.data(), result_buffer.data(), size);
|
||||
|
||||
return output;
|
||||
}
|
64
src/main.cpp
Normal file → Executable file
64
src/main.cpp
Normal file → Executable file
|
@ -9,6 +9,7 @@
|
|||
#include <QMessageBox>
|
||||
#include <QApplication>
|
||||
#include <QtQuick/QQuickWindow>
|
||||
#include <QtWebEngine/QtWebEngine>
|
||||
|
||||
#include "eventmodel.h"
|
||||
#include "membermodel.h"
|
||||
|
@ -19,23 +20,58 @@
|
|||
#include "community.h"
|
||||
#include "roomlistsortmodel.h"
|
||||
#include "emote.h"
|
||||
#include "appcore.h"
|
||||
|
||||
QNetworkAccessManager* network::manager;
|
||||
QString network::homeserverURL, network::accessToken;
|
||||
void AppCore::addAccount(QString profileName) {
|
||||
accounts.push_back(new MatrixCore(profileName));
|
||||
|
||||
context->setContextProperty("matrix", accounts.back());
|
||||
|
||||
emit accountChange();
|
||||
}
|
||||
|
||||
void AppCore::switchAccount(QString profileName) {
|
||||
qDebug() << "switching to " << profileName;
|
||||
|
||||
for(auto account : accounts) {
|
||||
if(account->profileName == profileName) {
|
||||
qDebug() << account->profileName << " = " << profileName;
|
||||
context->setContextProperty("matrix", account);
|
||||
}
|
||||
}
|
||||
|
||||
emit accountChange();
|
||||
}
|
||||
|
||||
QVariantList AppCore::getAccounts() {
|
||||
QVariantList list;
|
||||
for(auto account : accounts)
|
||||
list.push_back(QVariant::fromValue(account));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
QApplication app2(argc, argv);
|
||||
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QCoreApplication::setApplicationName("Trinity");
|
||||
QCoreApplication::setOrganizationName("redstrate");
|
||||
|
||||
QtWebEngine::initialize();
|
||||
|
||||
QApplication app2(argc, argv);
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.addHelpOption();
|
||||
|
||||
QCommandLineOption profileOption("profile", "Default profile to load", "profile");
|
||||
|
||||
parser.addOption(profileOption);
|
||||
parser.process(app2);
|
||||
|
||||
qRegisterMetaType<RequestSender>();
|
||||
|
||||
network::manager = new QNetworkAccessManager();
|
||||
|
||||
network::homeserverURL = "https://matrix.org";
|
||||
|
||||
// matrix
|
||||
qmlRegisterUncreatableType<AppCore>("trinity.matrix", 1, 0, "AppCore", "");
|
||||
qmlRegisterUncreatableType<EventModel>("trinity.matrix", 1, 0, "EventModel", "");
|
||||
qmlRegisterUncreatableType<MatrixCore>("trinity.matrix", 1, 0, "MatrixCore", "");
|
||||
qmlRegisterUncreatableType<Room>("trinity.matrix", 1, 0, "Room", "");
|
||||
|
@ -51,7 +87,15 @@ int main(int argc, char* argv[]) {
|
|||
QQmlApplicationEngine engine;
|
||||
QQmlContext* context = new QQmlContext(engine.rootContext(), &engine);
|
||||
|
||||
MatrixCore matrix;
|
||||
AppCore* core = new AppCore();
|
||||
core->context = context;
|
||||
|
||||
if(parser.isSet(profileOption)) {
|
||||
core->addAccount(parser.value(profileOption));
|
||||
} else {
|
||||
core->addAccount();
|
||||
}
|
||||
|
||||
Desktop desktop;
|
||||
|
||||
QSystemTrayIcon* trayIcon = new QSystemTrayIcon();
|
||||
|
@ -59,7 +103,7 @@ int main(int argc, char* argv[]) {
|
|||
desktop.icon = trayIcon;
|
||||
|
||||
context->setContextProperty("desktop", &desktop);
|
||||
context->setContextProperty("matrix", &matrix);
|
||||
context->setContextProperty("app", core);
|
||||
|
||||
QQmlComponent component(&engine);
|
||||
component.loadUrl(QUrl("qrc:/main.qml"));
|
||||
|
|
545
src/matrixcore.cpp
Normal file → Executable file
545
src/matrixcore.cpp
Normal file → Executable file
|
@ -10,18 +10,31 @@
|
|||
#include <QDir>
|
||||
#include <QMimeDatabase>
|
||||
#include <QMimeData>
|
||||
|
||||
#include <fstream>
|
||||
#include <iterator>
|
||||
#include "network.h"
|
||||
#include "community.h"
|
||||
|
||||
MatrixCore::MatrixCore(QObject* parent) : QObject(parent), roomListModel(rooms), directoryListModel(publicRooms), eventModel(*this) {
|
||||
MatrixCore::MatrixCore(QString profileName, QObject* parent) : QObject(parent), profileName(profileName), roomListModel(rooms), directoryListModel(publicRooms), eventModel(*this) {
|
||||
network = new Network();
|
||||
encryption = new Encryption();
|
||||
|
||||
QSettings settings;
|
||||
settings.beginGroup(profileName);
|
||||
homeserverURL = settings.value("homeserver", "matrix.org").toString();
|
||||
userId = settings.value("userId").toString();
|
||||
network::homeserverURL = "https://" + homeserverURL;
|
||||
network->homeserverURL = "https://" + homeserverURL;
|
||||
deviceId = settings.value("deviceId").toString();
|
||||
|
||||
if(settings.contains("accessToken"))
|
||||
network::accessToken = "Bearer " + settings.value("accessToken").toString();
|
||||
network->accessToken = "Bearer " + settings.value("accessToken").toString();
|
||||
|
||||
if(settings.contains("accountPickle")) {
|
||||
encryption->loadDeviceKeys(settings.value("accountPickle").toString());
|
||||
qDebug() << "testing identity key: " << encryption->identityKey;
|
||||
}
|
||||
|
||||
settings.endGroup();
|
||||
|
||||
emptyRoom.setName("Empty");
|
||||
emptyRoom.setTopic("There is nothing here.");
|
||||
|
@ -33,6 +46,13 @@ MatrixCore::MatrixCore(QObject* parent) : QObject(parent), roomListModel(rooms),
|
|||
roomListSortModel.sort(0);
|
||||
});
|
||||
|
||||
memberSortModel.setSourceModel(&memberModel);
|
||||
memberSortModel.setSortRole(RoomListModel::SectionRole);
|
||||
|
||||
connect(this, &MatrixCore::currentRoomChanged, [this] {
|
||||
memberSortModel.sort(0);
|
||||
});
|
||||
|
||||
directoryListSortModel.setSourceModel(&directoryListModel);
|
||||
|
||||
updateAccountInformation();
|
||||
|
@ -74,7 +94,7 @@ void MatrixCore::registerAccount(const QString &username, const QString &passwor
|
|||
{"password", password}
|
||||
};
|
||||
|
||||
network::postJSON("/_matrix/client/r0/register?kind=user", registerObject, [this](QNetworkReply* reply) {
|
||||
network->postJSON("/_matrix/client/r0/register?kind=user", registerObject, [this](QNetworkReply* reply) {
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
if(reply->error()) {
|
||||
|
@ -103,12 +123,14 @@ void MatrixCore::registerAccount(const QString &username, const QString &passwor
|
|||
emit registerAttempt(true, document.object()["error"].toString());
|
||||
}
|
||||
} else {
|
||||
network::accessToken = "Bearer " + document.object()["access_token"].toString();
|
||||
network->accessToken = "Bearer " + document.object()["access_token"].toString();
|
||||
|
||||
QSettings settings;
|
||||
settings.beginGroup(profileName);
|
||||
settings.setValue("accessToken", document.object()["access_token"].toString());
|
||||
settings.setValue("userId", document.object()["user_id"].toString());
|
||||
settings.setValue("deviceId", document.object()["device_id"].toString());
|
||||
settings.endGroup();
|
||||
|
||||
emit registerAttempt(false, "");
|
||||
}
|
||||
|
@ -123,26 +145,83 @@ void MatrixCore::login(const QString& username, const QString& password) {
|
|||
{"initial_device_display_name", "Trinity"}
|
||||
};
|
||||
|
||||
network::postJSON("/_matrix/client/r0/login", loginObject, [this](QNetworkReply* reply) {
|
||||
network->postJSON("/_matrix/client/r0/login", loginObject, [this](QNetworkReply* reply) {
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
if(reply->error()) {
|
||||
emit loginAttempt(true, document.object()["error"].toString());
|
||||
} else {
|
||||
network::accessToken = "Bearer " + document.object()["access_token"].toString();
|
||||
network->accessToken = "Bearer " + document.object()["access_token"].toString();
|
||||
|
||||
QSettings settings;
|
||||
settings.beginGroup(profileName);
|
||||
settings.setValue("accessToken", document.object()["access_token"].toString());
|
||||
settings.setValue("userId", document.object()["user_id"].toString());
|
||||
settings.setValue("deviceId", document.object()["device_id"].toString());
|
||||
|
||||
// upload keys
|
||||
encryption->createNewDeviceKeys();
|
||||
|
||||
QJsonObject keysObject {
|
||||
{"curve25519:" + document.object()["device_id"].toString(), encryption->identityKey["curve25519"]},
|
||||
{"ed25519:" + document.object()["device_id"].toString(), encryption->identityKey["ed25519"]}
|
||||
};
|
||||
|
||||
QJsonObject deviceKeysObject {
|
||||
{"user_id", document.object()["user_id"].toString()},
|
||||
{"device_id", document.object()["device_id"].toString()},
|
||||
{"algorithms", QJsonArray({"m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"})},
|
||||
{"keys", keysObject}
|
||||
};
|
||||
|
||||
QJsonObject signature {
|
||||
{"ed25519:" + document.object()["device_id"].toString(), encryption->signMessage(QJsonDocument(deviceKeysObject).toJson(QJsonDocument::Compact)) }};
|
||||
|
||||
deviceKeysObject["signatures"] = QJsonObject {
|
||||
{document.object()["user_id"].toString(), signature}};
|
||||
|
||||
QJsonObject oneTimeKeyObject;
|
||||
|
||||
auto one_time_keys = encryption->generateOneTimeKeys(encryption->getRecommendedNumberOfOneTimeKeys());
|
||||
|
||||
for(auto key : one_time_keys["curve25519"].toObject().keys()) {
|
||||
QJsonObject keyObject {
|
||||
{"key", one_time_keys["curve25519"].toObject()[key]}
|
||||
};
|
||||
|
||||
QJsonObject signature {
|
||||
{"ed25519:" + document.object()["device_id"].toString(), encryption->signMessage(QJsonDocument(keyObject).toJson(QJsonDocument::Compact)) }};
|
||||
|
||||
keyObject["signatures"] = QJsonObject {
|
||||
{document.object()["user_id"].toString(), signature}};
|
||||
|
||||
oneTimeKeyObject["signed_curve25519:" + key] = keyObject;
|
||||
}
|
||||
|
||||
QJsonObject masterKeysObject {
|
||||
{"device_keys", deviceKeysObject},
|
||||
{"one_time_keys", oneTimeKeyObject}
|
||||
};
|
||||
|
||||
//qDebug() << masterKeysObject;
|
||||
|
||||
auto keys = encryption->saveDeviceKeys();
|
||||
|
||||
settings.setValue("accountPickle", keys);
|
||||
settings.endGroup();
|
||||
|
||||
network->postJSON("/_matrix/client/r0/keys/upload", masterKeysObject, [this](QNetworkReply* reply) {
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
qDebug() << "KEY UPLOAD RESULT: " << document;
|
||||
});
|
||||
|
||||
emit loginAttempt(false, "");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void MatrixCore::logout() {
|
||||
network::post("/_matrix/client/r0/logout");
|
||||
network->post("/_matrix/client/r0/logout");
|
||||
|
||||
QSettings settings;
|
||||
settings.remove("accessToken");
|
||||
|
@ -152,7 +231,7 @@ void MatrixCore::logout() {
|
|||
}
|
||||
|
||||
void MatrixCore::updateAccountInformation() {
|
||||
network::get("/_matrix/client/r0/profile/" + userId + "/displayname", [this](QNetworkReply* reply) {
|
||||
network->get("/_matrix/client/r0/profile/" + userId + "/displayname", [this](QNetworkReply* reply) {
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
displayName = document.object()["displayname"].toString();
|
||||
|
@ -167,26 +246,98 @@ void MatrixCore::setDisplayName(const QString& name) {
|
|||
{"displayname", name}
|
||||
};
|
||||
|
||||
network::putJSON("/_matrix/client/r0/profile/" + userId + "/displayname", displayNameObject, [this, name](QNetworkReply* reply) {
|
||||
network->putJSON("/_matrix/client/r0/profile/" + userId + "/displayname", displayNameObject, [this, name](QNetworkReply* reply) {
|
||||
emit displayNameChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void MatrixCore::sync() {
|
||||
if(network::accessToken.isEmpty())
|
||||
if(network->accessToken.isEmpty())
|
||||
return;
|
||||
|
||||
QString url = "/_matrix/client/r0/sync";
|
||||
if(!nextBatch.isEmpty())
|
||||
url += "?since=" + nextBatch;
|
||||
|
||||
network::get(url, [this](QNetworkReply* reply) {
|
||||
network->get(url, [this](QNetworkReply* reply) {
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
if(!document.object()["next_batch"].isNull())
|
||||
nextBatch = document.object()["next_batch"].toString();
|
||||
|
||||
const auto createRoom = [this](const QString id, const QString joinState) {
|
||||
//qDebug() << document.object()["to_device"];
|
||||
//qDebug() << document.object()["device_one_time_keys_count"];
|
||||
|
||||
for(auto event : document.object()["to_device"].toObject()["events"].toArray()) {
|
||||
if(event.toObject()["type"] == "m.room_key_request" && event.toObject()["content"].toObject()["action"] == "request") {
|
||||
auto sender = event.toObject()["sender"].toString();
|
||||
auto device_id = event.toObject()["content"].toObject()["requesting_device_id"].toString();
|
||||
auto room_id = event.toObject()["content"].toObject()["body"].toObject()["room_id"].toString();
|
||||
|
||||
QJsonObject queryObject {
|
||||
{"timeout", 10000},
|
||||
{"device_keys", QJsonObject{
|
||||
{sender, QJsonArray({device_id})}
|
||||
}},
|
||||
{"token", "string"},
|
||||
};
|
||||
|
||||
network->postJSON("/_matrix/client/r0/keys/query", queryObject, [this, event, sender, device_id, room_id](QNetworkReply* reply) {
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
auto senderCurveKey = document.object()["device_keys"].toObject()[sender].toObject()[device_id].toObject()["keys"].toObject()["curve25519:" + device_id].toString();
|
||||
auto senderEdKey = document.object()["device_keys"].toObject()[sender].toObject()[device_id].toObject()["keys"].toObject()["ed25519:" + device_id].toString();
|
||||
|
||||
qDebug() << "sending keys to " << device_id;
|
||||
|
||||
createOrLoadSession();
|
||||
|
||||
sendKeyToDevice(room_id, senderCurveKey, senderEdKey, currentSessionId, currentSessionKey, sender, device_id);
|
||||
});
|
||||
} else if(event.toObject()["type"] == "m.room_key") {
|
||||
qDebug() << "we recieved a new key from a user in the room :-)";
|
||||
} else if(event.toObject()["type"] == "m.forwarded_room_key") {
|
||||
qDebug() << "we recieved a new key from a user in the room :-)";
|
||||
} else if(event.toObject()["type"] == "m.room.encrypted") {
|
||||
auto curveKey = event.toObject()["content"].toObject()["ciphertext"].toObject().keys()[0];
|
||||
auto senderKey = event.toObject()["content"].toObject()["sender_key"].toString();
|
||||
int type = event.toObject()["content"].toObject()["ciphertext"].toObject()[curveKey].toObject()["type"].toInt();
|
||||
auto body = event.toObject()["content"].toObject()["ciphertext"].toObject()[curveKey].toObject()["body"].toString();
|
||||
|
||||
// create a new inbound session
|
||||
auto session = encryption->createInboundSession(senderKey.toStdString(), body.toStdString());
|
||||
|
||||
auto decryptedMsg = encryption->decrypt(session, type, body.toStdString());
|
||||
|
||||
const QJsonDocument document = QJsonDocument::fromJson(QByteArray(reinterpret_cast<const char*>(decryptedMsg.data()), decryptedMsg.size()));
|
||||
auto id = document.object()["content"].toObject()["session_id"].toString();
|
||||
|
||||
qDebug() << "NEW KEY " << id << " = " << document;
|
||||
|
||||
// create new inbound session, append to list
|
||||
auto sess = encryption->beginInboundSession(document.object()["content"].toObject()["session_key"].toString().toStdString());
|
||||
inboundSessions[id] = sess;
|
||||
|
||||
// if we recieved a new key, let's see if we can decrypt some old messages!
|
||||
for(auto room : rooms) {
|
||||
if(room->getId() == document.object()["content"].toObject()["room_id"].toString()) {
|
||||
for(auto event : room->events) {
|
||||
if(event->encryptionInfo != nullptr && event->encryptionInfo->sessionId == id) {
|
||||
auto msg = encryption->decrypt(sess, event->encryptionInfo->cipherText.toStdString());
|
||||
|
||||
const QJsonDocument document = QJsonDocument::fromJson(QByteArray(reinterpret_cast<const char*>(msg.data()), msg.size()));
|
||||
|
||||
populateEvent(document.object(), event);
|
||||
|
||||
emit event->msgChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto createRoom = [this](const QString id, const QString joinState, bool autofill_data = true) {
|
||||
roomListModel.beginInsertRoom();
|
||||
|
||||
Room* room = new Room(this);
|
||||
|
@ -201,7 +352,8 @@ void MatrixCore::sync() {
|
|||
room->setNotificationLevel(1);
|
||||
settings.endGroup();
|
||||
|
||||
network::get("/_matrix/client/r0/rooms/" + id + "/state/m.room.name", [this, room](QNetworkReply* reply) {
|
||||
if(autofill_data) {
|
||||
network->get("/_matrix/client/r0/rooms/" + id + "/state/m.room.name", [this, room](QNetworkReply* reply) {
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
if(document.object()["errcode"].toString() == "M_GUEST_ACCESS_FORBIDDEN") {
|
||||
room->setGuestDenied(true);
|
||||
|
@ -214,7 +366,7 @@ void MatrixCore::sync() {
|
|||
roomListModel.updateRoom(room);
|
||||
});
|
||||
|
||||
network::get("/_matrix/client/r0/rooms/" + id + "/state/m.room.topic", [this, room](QNetworkReply* reply) {
|
||||
network->get("/_matrix/client/r0/rooms/" + id + "/state/m.room.topic", [this, room](QNetworkReply* reply) {
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
if(document.object()["errcode"].toString() == "M_GUEST_ACCESS_FORBIDDEN") {
|
||||
room->setGuestDenied(true);
|
||||
|
@ -226,7 +378,7 @@ void MatrixCore::sync() {
|
|||
roomListModel.updateRoom(room);
|
||||
});
|
||||
|
||||
network::get("/_matrix/client/r0/rooms/" + id + "/state/m.room.avatar", [this, room](QNetworkReply* reply) {
|
||||
network->get("/_matrix/client/r0/rooms/" + id + "/state/m.room.avatar", [this, room](QNetworkReply* reply) {
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
if(document.object()["errcode"].toString() == "M_GUEST_ACCESS_FORBIDDEN") {
|
||||
room->setGuestDenied(true);
|
||||
|
@ -235,12 +387,31 @@ void MatrixCore::sync() {
|
|||
|
||||
if(document.object().contains("url")) {
|
||||
const QString imageId = document.object()["url"].toString().remove("mxc://");
|
||||
room->setAvatar(network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
||||
room->setAvatar(network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
||||
}
|
||||
|
||||
roomListModel.updateRoom(room);
|
||||
});
|
||||
|
||||
network->get("/_matrix/client/r0/rooms/" + id + "/state/m.room.power_levels", [this, room](QNetworkReply* reply) {
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
for(auto user : document.object()["users"].toObject().keys()) {
|
||||
room->powerLevelList[user] = document.object()["users"].toObject()[user].toInt();
|
||||
}
|
||||
});
|
||||
|
||||
network->get("/_matrix/client/r0/rooms/" + id + "/state/m.room.encryption", [this, room](QNetworkReply* reply) {
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
if(document.object().contains("algorithm")) {
|
||||
room->setEncrypted();
|
||||
}
|
||||
|
||||
//qDebug() << document;
|
||||
});
|
||||
}
|
||||
|
||||
rooms.push_back(room);
|
||||
idToRoom.insert(id, room);
|
||||
|
||||
|
@ -255,7 +426,8 @@ void MatrixCore::sync() {
|
|||
|
||||
for(const auto id : document.object()["rooms"].toObject()["invite"].toObject().keys()) {
|
||||
if(!invitedRooms.count(id)) {
|
||||
Room* room = createRoom(id, "Invited");
|
||||
Room* room = createRoom(id, "Invited", false);
|
||||
room->setGuestDenied(true);
|
||||
|
||||
for(auto event : document.object()["rooms"].toObject()["invite"].toObject()[id].toObject()["invite_state"].toObject()["events"].toArray()) {
|
||||
const QString type = event.toObject()["type"].toString();
|
||||
|
@ -266,7 +438,7 @@ void MatrixCore::sync() {
|
|||
room->setName(event.toObject()["content"].toObject()["name"].toString());
|
||||
} else if(type == "m.room.avatar") {
|
||||
const QString imageId = event.toObject()["content"].toObject()["url"].toString().remove("mxc://");
|
||||
room->setAvatar(network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
||||
room->setAvatar(network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
||||
}
|
||||
|
||||
roomListModel.updateRoom(room);
|
||||
|
@ -393,6 +565,10 @@ void MatrixCore::sync() {
|
|||
});
|
||||
}
|
||||
|
||||
bool MatrixCore::isInitialSyncComplete() {
|
||||
return !firstSync;
|
||||
}
|
||||
|
||||
void MatrixCore::sendMessage(Room* room, const QString& message) {
|
||||
if(message.isEmpty())
|
||||
return;
|
||||
|
@ -437,8 +613,9 @@ void MatrixCore::sendMessage(Room* room, const QString& message) {
|
|||
shouldSendAsMarkdown = strlen(formatted) > 8 + message.length();
|
||||
}
|
||||
|
||||
QJsonObject messageObject;
|
||||
if(shouldSendAsMarkdown) {
|
||||
const QJsonObject messageObject {
|
||||
messageObject = QJsonObject {
|
||||
{"msgtype", "m.text"},
|
||||
{"formatted_body", formatted},
|
||||
{"body", message},
|
||||
|
@ -446,15 +623,80 @@ void MatrixCore::sendMessage(Room* room, const QString& message) {
|
|||
};
|
||||
|
||||
e->setMsg(formatted);
|
||||
|
||||
network::putJSON("/_matrix/client/r0/rooms/" + room->getId() + "/send/m.room.message/" + QRandomGenerator::global()->generate(), messageObject, onMessageFeedbackReceived);
|
||||
} else {
|
||||
const QJsonObject messageObject {
|
||||
messageObject = QJsonObject {
|
||||
{"msgtype", "m.text"},
|
||||
{"body", message}
|
||||
};
|
||||
|
||||
network::putJSON(QString("/_matrix/client/r0/rooms/" + room->getId() + "/send/m.room.message/") + QRandomGenerator::global()->generate(), messageObject, onMessageFeedbackReceived);
|
||||
e->setMsg(message);
|
||||
}
|
||||
|
||||
if(room->getEncrypted()) {
|
||||
// create megolm session
|
||||
createOrLoadSession();
|
||||
|
||||
/*QJsonObject deviceKeys;
|
||||
|
||||
// get device list for each user
|
||||
for(auto member : room->members) {
|
||||
deviceKeys.insert(member->getId(), QJsonArray());
|
||||
}
|
||||
|
||||
QJsonObject queryObject {
|
||||
{"timeout", 10000},
|
||||
{"device_keys", deviceKeys},
|
||||
{"token", "string"},
|
||||
};
|
||||
|
||||
network->postJSON("/_matrix/client/r0/keys/query", queryObject, [this, room](QNetworkReply* reply) {
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
// for each user, and for each of their devices, start a new olm session
|
||||
for(auto user_id : document.object()["device_keys"].toObject().keys()) {
|
||||
for(auto device_id : document.object()["device_keys"].toObject()[user_id].toObject().keys()) {
|
||||
QJsonObject claimObject {
|
||||
{"timeout", 10000},
|
||||
{"one_time_keys", QJsonObject {
|
||||
{user_id, QJsonObject {
|
||||
{device_id, "signed_curve25519"}
|
||||
}}
|
||||
}}
|
||||
};
|
||||
|
||||
std::string identityKey = document.object()["device_keys"].toObject()[user_id].toObject()[device_id].toObject()["keys"].toObject()[QString("curve25519:") + device_id].toString().toStdString();
|
||||
|
||||
std::string edIdentityKey = document.object()["device_keys"].toObject()[user_id].toObject()[device_id].toObject()["keys"].toObject()[QString("ed25519:") + device_id].toString().toStdString();
|
||||
|
||||
sendKeyToDevice(room->getId(), identityKey.c_str(), edIdentityKey.c_str(), currentSessionId, currentSessionKey, user_id, device_id);
|
||||
}
|
||||
}
|
||||
});*/
|
||||
|
||||
QJsonObject trueObject {
|
||||
{"room_id", room->getId()},
|
||||
{"type", "m.room.message"},
|
||||
{"content", messageObject}
|
||||
/*{"keys", QJsonObject {
|
||||
{"ed25519", encryption->identityKey["ed25519"]}
|
||||
}},
|
||||
{"sender", userId},
|
||||
{"sender_device", deviceId}*/
|
||||
};
|
||||
|
||||
// construct the m.room.encrypted event
|
||||
const QJsonObject roomEncryptedObject {
|
||||
{"algorithm", "m.megolm.v1.aes-sha2"},
|
||||
{"ciphertext", encryption->encryptGroup(currentSession, QString(QJsonDocument(trueObject).toJson(QJsonDocument::Compact)).toStdString()).c_str()},
|
||||
{"sender_key", encryption->identityKey["curve25519"]},
|
||||
{"session_id", currentSessionId},
|
||||
{"device_id", deviceId}
|
||||
};
|
||||
|
||||
network->putJSON("/_matrix/client/r0/rooms/" + room->getId() + "/send/m.room.encrypted/" + QRandomGenerator::global()->generate(), roomEncryptedObject, [](QNetworkReply* reply) {
|
||||
//qDebug() << "reply from room send: " << reply->readAll();
|
||||
});
|
||||
} else {
|
||||
network->putJSON(QString("/_matrix/client/r0/rooms/" + room->getId() + "/send/m.room.message/") + QString::number(QRandomGenerator::global()->generate()), messageObject, onMessageFeedbackReceived);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -463,7 +705,7 @@ void MatrixCore::removeMessage(const QString& eventId) {
|
|||
{"reason", ""}
|
||||
};
|
||||
|
||||
network::putJSON("/_matrix/client/r0/rooms/" + currentRoom->getId() + "/redact/" + eventId + "/" + QRandomGenerator::global()->generate(), reasonObject, [this, eventId](QNetworkReply* reply) {
|
||||
network->putJSON("/_matrix/client/r0/rooms/" + currentRoom->getId() + "/redact/" + eventId + "/" + QString::number(QRandomGenerator::global()->generate()), reasonObject, [this, eventId](QNetworkReply* reply) {
|
||||
auto& events = currentRoom->events;
|
||||
for(int i = 0; i < events.size(); i++) {
|
||||
if(events[i]->eventId == eventId) {
|
||||
|
@ -506,7 +748,7 @@ void MatrixCore::uploadAttachment(Room* room, const QString& path) {
|
|||
e->setMsg(fileName);
|
||||
e->setMsgType(mimeType.name().contains("image") ? "image" : "file");
|
||||
|
||||
network::postBinary("/_matrix/media/r0/upload?filename=" + f.fileName(), f.readAll(), mimeType.name(), [this, mimeType, fileName, fileSize, e](QNetworkReply* reply) {
|
||||
network->postBinary("/_matrix/media/r0/upload?filename=" + f.fileName(), f.readAll(), mimeType.name(), [this, mimeType, fileName, fileSize, e](QNetworkReply* reply) {
|
||||
if(!reply->error()) {
|
||||
e->setSent(true);
|
||||
|
||||
|
@ -524,7 +766,7 @@ void MatrixCore::uploadAttachment(Room* room, const QString& path) {
|
|||
{"info", infoObject}
|
||||
};
|
||||
|
||||
network::putJSON("/_matrix/client/r0/rooms/" + currentRoom->getId() + "/send/m.room.message/" + QRandomGenerator::global()->generate(), imageObject);
|
||||
network->putJSON("/_matrix/client/r0/rooms/" + currentRoom->getId() + "/send/m.room.message/" + QString::number(QRandomGenerator::global()->generate()), imageObject);
|
||||
}
|
||||
}, [e](qint64 sent, qint64 total) {
|
||||
e->setSentProgress((double)sent / (double)total);
|
||||
|
@ -540,7 +782,7 @@ void MatrixCore::startDirectChat(const QString& id) {
|
|||
{"invite", QJsonArray{id}}
|
||||
};
|
||||
|
||||
network::postJSON("/_matrix/client/r0/createRoom", roomObject, [](QNetworkReply*) {});
|
||||
network->postJSON("/_matrix/client/r0/createRoom", roomObject, [](QNetworkReply*) {});
|
||||
}
|
||||
|
||||
void MatrixCore::setTyping(Room* room) {
|
||||
|
@ -549,11 +791,11 @@ void MatrixCore::setTyping(Room* room) {
|
|||
{"timeout", 15000}
|
||||
};
|
||||
|
||||
network::putJSON("/_matrix/client/r0/rooms/" + room->getId() + "/typing/" + userId, typingObject);
|
||||
network->putJSON("/_matrix/client/r0/rooms/" + room->getId() + "/typing/" + userId, typingObject);
|
||||
}
|
||||
|
||||
void MatrixCore::joinRoom(const QString& id) {
|
||||
network::post("/_matrix/client/r0/rooms/" + id + "/join", [this, id](QNetworkReply* reply) {
|
||||
network->post("/_matrix/client/r0/rooms/" + id + "/join", [this, id](QNetworkReply* reply) {
|
||||
if(!reply->error()) {
|
||||
//check if its by an invite
|
||||
if(invitedRooms.contains(id)) {
|
||||
|
@ -576,7 +818,7 @@ void MatrixCore::joinRoom(const QString& id) {
|
|||
}
|
||||
|
||||
void MatrixCore::leaveRoom(const QString& id) {
|
||||
network::post("/_matrix/client/r0/rooms/" + id + "/leave");
|
||||
network->post("/_matrix/client/r0/rooms/" + id + "/leave");
|
||||
}
|
||||
|
||||
void MatrixCore::inviteToRoom(Room* room, const QString& userId) {
|
||||
|
@ -584,14 +826,14 @@ void MatrixCore::inviteToRoom(Room* room, const QString& userId) {
|
|||
{"user_id", userId}
|
||||
};
|
||||
|
||||
network::postJSON("/_matrix/client/r0/rooms/" + room->getId() + "/invite", inviteObject, [](QNetworkReply*) {});
|
||||
network->postJSON("/_matrix/client/r0/rooms/" + room->getId() + "/invite", inviteObject, [](QNetworkReply*) {});
|
||||
}
|
||||
|
||||
void MatrixCore::updateMembers(Room* room) {
|
||||
if(!room)
|
||||
return;
|
||||
|
||||
network::get("/_matrix/client/r0/rooms/" + room->getId() + "/members", [this, room](QNetworkReply* reply) {
|
||||
network->get("/_matrix/client/r0/rooms/" + room->getId() + "/members", [this, room](QNetworkReply* reply) {
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
const QJsonArray& chunk = document.object()["chunk"].toArray();
|
||||
|
@ -620,7 +862,7 @@ void MatrixCore::updateMembers(Room* room) {
|
|||
|
||||
if(!memberJson["content"].toObject()["avatar_url"].isNull()) {
|
||||
const QString imageId = memberJson["content"].toObject()["avatar_url"].toString().remove("mxc://");
|
||||
m->setAvatar(network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
||||
m->setAvatar(network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
||||
}
|
||||
|
||||
idToMember.insert(id, m);
|
||||
|
@ -648,7 +890,7 @@ void MatrixCore::readMessageHistory(Room* room) {
|
|||
if(!room || room->prevBatch.isEmpty())
|
||||
return;
|
||||
|
||||
network::get("/_matrix/client/r0/rooms/" + room->getId() + "/messages?from=" + room->prevBatch + "&dir=b", [this, room](QNetworkReply* reply) {
|
||||
network->get("/_matrix/client/r0/rooms/" + room->getId() + "/messages?from=" + room->prevBatch + "&dir=b", [this, room](QNetworkReply* reply) {
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
room->prevBatch = document.object()["end"].toString();
|
||||
|
@ -674,7 +916,7 @@ void MatrixCore::updateMemberCommunities(Member* member) {
|
|||
{"user_ids", userIdsArray}
|
||||
};
|
||||
|
||||
network::postJSON("/_matrix/client/r0/publicised_groups", userIdsObject, [this, member](QNetworkReply* reply) {
|
||||
network->postJSON("/_matrix/client/r0/publicised_groups", userIdsObject, [this, member](QNetworkReply* reply) {
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
for(const auto id : document.object()["users"].toObject()[member->getId()].toArray()) {
|
||||
|
@ -698,22 +940,23 @@ void MatrixCore::updateMemberCommunities(Member* member) {
|
|||
}
|
||||
|
||||
bool MatrixCore::settingsValid() {
|
||||
QSettings settings;
|
||||
return settings.contains("accessToken");
|
||||
return !network->accessToken.isEmpty() && !network->homeserverURL.isEmpty();
|
||||
}
|
||||
|
||||
void MatrixCore::setHomeserver(const QString& url) {
|
||||
network::homeserverURL = "https://" + url;
|
||||
network->homeserverURL = "https://" + url;
|
||||
|
||||
network::get("/_matrix/client/versions", [this, url](QNetworkReply* reply) {
|
||||
network->get("/_matrix/client/versions", [this, url](QNetworkReply* reply) {
|
||||
if(!reply->error()) {
|
||||
homeserverURL = url;
|
||||
|
||||
QSettings settings;
|
||||
settings.beginGroup(profileName);
|
||||
settings.setValue("homeserver", url);
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
network::homeserverURL = "https://" + homeserverURL;
|
||||
network->homeserverURL = "https://" + homeserverURL;
|
||||
|
||||
emit homeserverChanged(reply->error() == 0, reply->errorString());
|
||||
});
|
||||
|
@ -798,10 +1041,10 @@ QString MatrixCore::getUsername() const {
|
|||
return id.remove('@').split(':')[0];
|
||||
}
|
||||
|
||||
void MatrixCore::loadDirectory() {
|
||||
void MatrixCore::loadDirectory(const QString& homeserver) {
|
||||
const QJsonObject bodyObject;
|
||||
|
||||
network::postJSON("/_matrix/client/r0/publicRooms", bodyObject, [this](QNetworkReply* reply) {
|
||||
network->postJSON("/_matrix/client/r0/publicRooms?server=" + homeserver, bodyObject, [this](QNetworkReply* reply) {
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
if(publicRooms.size() != document.object()["chunk"].toArray().size()) {
|
||||
|
@ -820,7 +1063,7 @@ void MatrixCore::loadDirectory() {
|
|||
|
||||
if(!roomObject["avatar_url"].isNull()) {
|
||||
const QString imageId = roomObject["avatar_url"].toString().remove("mxc://");
|
||||
r->setAvatar(network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
||||
r->setAvatar(network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
||||
}
|
||||
|
||||
r->setTopic(roomObject["topic"].toString());
|
||||
|
@ -850,7 +1093,7 @@ void MatrixCore::readUpTo(Room* room, const int index) {
|
|||
if(index < 0)
|
||||
return;
|
||||
|
||||
network::post("/_matrix/client/r0/rooms/" + room->getId() + "/receipt/m.read/" + room->events[index]->eventId);
|
||||
network->post("/_matrix/client/r0/rooms/" + room->getId() + "/receipt/m.read/" + room->events[index]->eventId);
|
||||
}
|
||||
|
||||
void MatrixCore::setMarkdownEnabled(const bool enabled) {
|
||||
|
@ -878,8 +1121,8 @@ EmoteListModel* MatrixCore::getLocalEmoteListModel() {
|
|||
return &localEmoteModel;
|
||||
}
|
||||
|
||||
MemberModel* MatrixCore::getMemberModel() {
|
||||
return &memberModel;
|
||||
MemberListSortModel* MatrixCore::getMemberModel() {
|
||||
return &memberSortModel;
|
||||
}
|
||||
|
||||
QString MatrixCore::getHomeserverURL() const {
|
||||
|
@ -910,7 +1153,7 @@ void MatrixCore::consumeEvent(const QJsonObject& event, Room& room, const bool i
|
|||
};
|
||||
|
||||
bool found = false;
|
||||
if(eventType == "m.room.message") {
|
||||
if(eventType == "m.room.message" || eventType == "m.room.encrypted") {
|
||||
for(size_t i = 0; i < unsentMessages.size(); i++) {
|
||||
if(event["sender"].toString() == userId && unsentMessages[i]->getRoom() == room.getId()) {
|
||||
found = true;
|
||||
|
@ -920,9 +1163,13 @@ void MatrixCore::consumeEvent(const QJsonObject& event, Room& room, const bool i
|
|||
eventModel.updateEvent(unsentMessages[i]);
|
||||
|
||||
unsentMessages.removeAt(i);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if(eventType == "m.room.member") {
|
||||
}
|
||||
|
||||
if(eventType == "m.room.member") {
|
||||
// avoid events tied to us
|
||||
if(event["state_key"].toString() == userId)
|
||||
return;
|
||||
|
@ -935,11 +1182,75 @@ void MatrixCore::consumeEvent(const QJsonObject& event, Room& room, const bool i
|
|||
|
||||
if(!event["content"].toObject()["avatar_url"].isNull()) {
|
||||
const QString imageId = event["content"].toObject()["avatar_url"].toString().remove("mxc://");
|
||||
room.setAvatar(network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
||||
room.setAvatar(network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
||||
}
|
||||
}
|
||||
|
||||
roomListModel.updateRoom(&room);
|
||||
} else if(eventType == "m.room.encrypted") {
|
||||
Event* e = new Event(&room);
|
||||
|
||||
if(event["content"].toObject()["device_id"] != deviceId) {
|
||||
EncryptionInformation* info = new EncryptionInformation();
|
||||
e->encryptionInfo = info;
|
||||
info->cipherText = event["content"].toObject()["ciphertext"].toString();
|
||||
info->sessionId = event["content"].toObject()["session_id"].toString();
|
||||
|
||||
if(!inboundSessions.contains(event["content"].toObject()["session_id"].toString())) {
|
||||
//qDebug() << "new encrypted event " << event;
|
||||
e->setMsg("** ERR: we can't decrypt this yet, we do not have the key.");
|
||||
|
||||
qDebug() << "the failed id: " << event["content"].toObject()["session_id"].toString();
|
||||
qDebug() << inboundSessions;
|
||||
|
||||
// let's send a room key request event
|
||||
// construct m.room.key event
|
||||
const QJsonObject roomKeyObject {
|
||||
{"action", "request"},
|
||||
{"body", QJsonObject {
|
||||
{"algorithm", event["content"].toObject()["algorithm"]},
|
||||
{"room_id", room.getId()},
|
||||
{"sender_key", event["content"].toObject()["sender_key"]},
|
||||
{"session_id", event["content"].toObject()["session_id"]}
|
||||
}},
|
||||
{"requesting_device_id", deviceId},
|
||||
{"request_id", QString("lololol") + QString::number(QRandomGenerator::global()->generate())}
|
||||
};
|
||||
|
||||
const QJsonObject sendToDeviceObject {
|
||||
{"messages", QJsonObject {
|
||||
{ event["sender"].toString(), QJsonObject {
|
||||
{ event["content"].toObject()["device_id"].toString(), roomKeyObject}
|
||||
}}
|
||||
}}
|
||||
};
|
||||
|
||||
//qDebug() << QJsonDocument(sendToDeviceObject).toJson(QJsonDocument::Compact);
|
||||
|
||||
network->putJSON(QString("/_matrix/client/r0/sendToDevice/m.room_key_request/") + QString::number(QRandomGenerator::global()->generate()), sendToDeviceObject, [](QNetworkReply* reply) {
|
||||
//qDebug() << "REPLY FROM KEY REQUEST SEND: " << reply->readAll();
|
||||
});
|
||||
} else {
|
||||
auto session = inboundSessions[event["content"].toObject()["session_id"].toString()];
|
||||
auto msg = encryption->decrypt(session, event["content"].toObject()["ciphertext"].toString().toStdString());
|
||||
|
||||
const QJsonDocument document = QJsonDocument::fromJson(QByteArray(reinterpret_cast<const char*>(msg.data()), msg.size()));
|
||||
|
||||
populateEvent(document.object(), e);
|
||||
|
||||
}
|
||||
} else {
|
||||
e->setMsg("** ERR: messages sent from the same device are not supported yet.");
|
||||
}
|
||||
|
||||
e->setSender("placeholder");
|
||||
e->eventId = event["event_id"].toString();
|
||||
e->setMsgType("text");
|
||||
|
||||
addEvent(e);
|
||||
|
||||
if(!firstSync && !traversingHistory)
|
||||
emit message(&room, e->getSender(), e->getMsg());
|
||||
} else
|
||||
return;
|
||||
|
||||
|
@ -948,16 +1259,21 @@ void MatrixCore::consumeEvent(const QJsonObject& event, Room& room, const bool i
|
|||
return;
|
||||
|
||||
if(!found && eventType == "m.room.message") {
|
||||
const QString msgType = event["content"].toObject()["msgtype"].toString();
|
||||
|
||||
Event* e = new Event(&room);
|
||||
|
||||
e->timestamp = QDateTime(QDate::currentDate(),
|
||||
QTime(QTime::currentTime().hour(),
|
||||
QTime::currentTime().minute(),
|
||||
QTime::currentTime().second(),
|
||||
QTime::currentTime().msec() - event["unsigned"].toObject()["age"].toInt()));
|
||||
populateEvent(event, e);
|
||||
|
||||
addEvent(e);
|
||||
|
||||
if(!firstSync && !traversingHistory)
|
||||
emit message(&room, e->getSender(), e->getMsg());
|
||||
}
|
||||
}
|
||||
|
||||
void MatrixCore::populateEvent(const QJsonObject& event, Event* e) {
|
||||
const QString msgType = event["content"].toObject()["msgtype"].toString();
|
||||
|
||||
e->timestamp = QDateTime::currentDateTime().addMSecs(-event["unsigned"].toObject()["age"].toInt());
|
||||
e->setSender(event["sender"].toString());
|
||||
e->eventId = event["event_id"].toString();
|
||||
|
||||
|
@ -992,19 +1308,13 @@ void MatrixCore::consumeEvent(const QJsonObject& event, Room& room, const bool i
|
|||
}
|
||||
|
||||
e->setMsg(msg);
|
||||
|
||||
addEvent(e);
|
||||
|
||||
if(!firstSync && !traversingHistory)
|
||||
emit message(&room, e->getSender(), e->getMsg());
|
||||
}
|
||||
}
|
||||
|
||||
Community* MatrixCore::createCommunity(const QString& id) {
|
||||
Community* community = new Community(this);
|
||||
community->setId(id);
|
||||
|
||||
network::get("/_matrix/client/r0/groups/" + community->getId() + "/summary", [this, community](QNetworkReply* reply) {
|
||||
network->get("/_matrix/client/r0/groups/" + community->getId() + "/summary", [this, community](QNetworkReply* reply) {
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
const QJsonObject& profile = document.object()["profile"].toObject();
|
||||
|
@ -1013,7 +1323,7 @@ Community* MatrixCore::createCommunity(const QString& id) {
|
|||
|
||||
if(!profile["avatar_url"].isNull()) {
|
||||
const QString imageId = profile["avatar_url"].toString().remove("mxc://");
|
||||
community->setAvatar(network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
||||
community->setAvatar(network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale");
|
||||
}
|
||||
|
||||
community->setShortDescription(profile["short_description"].toString());
|
||||
|
@ -1027,12 +1337,12 @@ Community* MatrixCore::createCommunity(const QString& id) {
|
|||
|
||||
QString MatrixCore::getMXCThumbnailURL(QString url) {
|
||||
const QString imageId = url.remove("mxc://");
|
||||
return network::homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale";
|
||||
return network->homeserverURL + "/_matrix/media/r0/thumbnail/" + imageId + "?width=64&height=64&method=scale";
|
||||
}
|
||||
|
||||
QString MatrixCore::getMXCMediaURL(QString url) {
|
||||
const QString imageId = url.remove("mxc://");
|
||||
return network::homeserverURL + "/_matrix/media/v1/download/" + imageId;
|
||||
return network->homeserverURL + "/_matrix/media/v1/download/" + imageId;
|
||||
}
|
||||
|
||||
QString MatrixCore::getDisplayName() const {
|
||||
|
@ -1054,3 +1364,102 @@ QString MatrixCore::getTypingText() const {
|
|||
bool MatrixCore::getMarkdownEnabled() const {
|
||||
return markdownEnabled;
|
||||
}
|
||||
|
||||
void MatrixCore::sendKeyToDevice(QString roomId, QString senderCurveIdentity, QString senderEdIdentity, QString session_id, QString session_key, QString user_id, QString device_id)
|
||||
{
|
||||
// why we would we send ourselves a key?
|
||||
if(device_id == deviceId)
|
||||
return;
|
||||
|
||||
QJsonObject claimObject {
|
||||
{"timeout", 10000},
|
||||
{"one_time_keys", QJsonObject {
|
||||
{user_id, QJsonObject {
|
||||
{device_id, "signed_curve25519"}
|
||||
}}
|
||||
}}
|
||||
};
|
||||
|
||||
network->postJSON("/_matrix/client/r0/keys/claim", claimObject, [this, device_id, user_id, senderCurveIdentity, senderEdIdentity, roomId, session_id, session_key](QNetworkReply* reply) {
|
||||
const QJsonDocument document = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
std::string identityKey = senderCurveIdentity.toStdString();
|
||||
|
||||
// we couldnt claim a key, skip
|
||||
if(document.object()["one_time_keys"].toObject()[user_id].toObject()[device_id].toObject().keys().empty())
|
||||
return;
|
||||
|
||||
std::string oneTimeKey = document.object()["one_time_keys"].toObject()[user_id].toObject()[device_id].toObject()[document.object()["one_time_keys"].toObject()[user_id].toObject()[device_id].toObject().keys()[0]].toObject()["key"].toString().toStdString();
|
||||
|
||||
auto session = encryption->beginOutboundOlmSession(identityKey, oneTimeKey);
|
||||
|
||||
// construct m.room.key event
|
||||
const QJsonObject roomKeyObject {
|
||||
{"content", QJsonObject {
|
||||
{"algorithm", "m.megolm.v1.aes-sha2"},
|
||||
{"room_id", roomId},
|
||||
{"session_id", session_id},
|
||||
{"session_key", session_key}
|
||||
}},
|
||||
{"type", "m.room_key"},
|
||||
{"keys", QJsonObject {
|
||||
{"ed25519", encryption->identityKey["ed25519"]}
|
||||
}},
|
||||
{"sender", userId},
|
||||
{"sender_device", deviceId},
|
||||
{"recipient", user_id},
|
||||
{"recipient_keys", QJsonObject {
|
||||
{"ed25519", senderEdIdentity}
|
||||
}}
|
||||
};
|
||||
|
||||
// construct the m.room.encrypted event
|
||||
const QJsonObject roomEncryptedObject {
|
||||
{"algorithm", "m.olm.v1.curve25519-aes-sha2"},
|
||||
{"ciphertext", QJsonObject {
|
||||
{identityKey.c_str(), QJsonObject {
|
||||
{"body", encryption->encrypt(session, QString(QJsonDocument(roomKeyObject).toJson(QJsonDocument::Compact)).toStdString()).c_str()},
|
||||
{"type", (int)olm_encrypt_message_type(session)}
|
||||
}}}},
|
||||
{"sender_key", encryption->identityKey["curve25519"]},
|
||||
};
|
||||
|
||||
const QJsonObject sendToDeviceObject {
|
||||
{"messages", QJsonObject {
|
||||
{ user_id, QJsonObject {
|
||||
{ device_id, roomEncryptedObject}
|
||||
}}
|
||||
}}
|
||||
};
|
||||
|
||||
network->putJSON(QString("/_matrix/client/r0/sendToDevice/m.room.encrypted/") + QString::number(QRandomGenerator::global()->generate()), sendToDeviceObject, [](QNetworkReply* reply) {
|
||||
//qDebug() << "REPLY FROM KEY SEND: " << reply->readAll();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void MatrixCore::createOrLoadSession() {
|
||||
if(currentSession != nullptr)
|
||||
return;
|
||||
|
||||
QSettings settings;
|
||||
settings.beginGroup(profileName);
|
||||
|
||||
if(settings.contains("sessionPickle")) {
|
||||
currentSession = encryption->loadSession(settings.value("sessionPickle").toString());
|
||||
currentSessionId = encryption->getGroupSessionId(currentSession).c_str();
|
||||
currentSessionKey = encryption->getGroupSessionKey(currentSession).c_str();
|
||||
} else {
|
||||
currentSession = encryption->beginOutboundSession();
|
||||
|
||||
auto session_id = encryption->getGroupSessionId(currentSession);
|
||||
auto session_key = encryption->getGroupSessionKey(currentSession);
|
||||
|
||||
//qDebug () << "CREATING NEW SESSION WITH ID " << session_id.c_str();
|
||||
|
||||
currentSessionId = session_id.c_str();
|
||||
currentSessionKey = session_key.c_str();
|
||||
}
|
||||
|
||||
settings.endGroup();
|
||||
}
|
||||
|
|
12
src/membermodel.cpp
Normal file → Executable file
12
src/membermodel.cpp
Normal file → Executable file
|
@ -20,8 +20,17 @@ QVariant MemberModel::data(const QModelIndex &index, int role) const {
|
|||
return room->members.at(index.row())->getDisplayName();
|
||||
else if(role == AvatarURLRole)
|
||||
return room->members.at(index.row())->getAvatar();
|
||||
else
|
||||
else if(role == IdRole)
|
||||
return room->members.at(index.row())->getId();
|
||||
else {
|
||||
int powerLevel = room->powerLevelList[room->members.at(index.row())->getId()];
|
||||
if(powerLevel == 100)
|
||||
return "Admin";
|
||||
else if(powerLevel == 50)
|
||||
return "Moderator";
|
||||
else
|
||||
return "User";
|
||||
}
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> MemberModel::roleNames() const {
|
||||
|
@ -29,6 +38,7 @@ QHash<int, QByteArray> MemberModel::roleNames() const {
|
|||
roles[DisplayNameRole] = "displayName";
|
||||
roles[AvatarURLRole] = "avatarURL";
|
||||
roles[IdRole] = "id";
|
||||
roles[SectionRole] = "section";
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
|
18
src/roomlistsortmodel.cpp
Normal file → Executable file
18
src/roomlistsortmodel.cpp
Normal file → Executable file
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "room.h"
|
||||
#include "roomlistmodel.h"
|
||||
#include "membermodel.h"
|
||||
|
||||
bool RoomListSortModel::lessThan(const QModelIndex& left, const QModelIndex& right) const {
|
||||
const QString sectionLeft = sourceModel()->data(left, RoomListModel::SectionRole).toString();
|
||||
|
@ -15,3 +16,20 @@ bool RoomListSortModel::lessThan(const QModelIndex& left, const QModelIndex& rig
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MemberListSortModel::lessThan(const QModelIndex& left, const QModelIndex& right) const {
|
||||
const QString sectionLeft = sourceModel()->data(left, MemberModel::SectionRole).toString();
|
||||
const QString sectionRight = sourceModel()->data(right, MemberModel::SectionRole).toString();
|
||||
|
||||
if(sectionLeft == "Admin" && sectionRight == "Moderator")
|
||||
return true;
|
||||
else if(sectionLeft == "Moderator" && sectionRight == "User")
|
||||
return true;
|
||||
else if(sectionLeft == "Admin" && sectionRight == "User")
|
||||
return true;
|
||||
|
||||
if(sectionLeft == sectionRight)
|
||||
return sourceModel()->data(left, MemberModel::DisplayNameRole).toString() < sourceModel()->data(right, MemberModel::DisplayNameRole).toString();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
Reference in a new issue