diff --git a/include/Cache.h b/include/Cache.h index 994a6da7..97133b0c 100644 --- a/include/Cache.h +++ b/include/Cache.h @@ -342,6 +342,8 @@ public: //! Mark a room that uses e2e encryption. void setEncryptedRoom(const std::string &room_id); + bool isRoomEncrypted(const std::string &room_id); + //! Save the public keys for a device. void saveDeviceKeys(const std::string &device_id); void getDeviceKeys(const std::string &device_id); diff --git a/include/dialogs/RoomSettings.hpp b/include/dialogs/RoomSettings.hpp index 9a01d5c9..6cab03b7 100644 --- a/include/dialogs/RoomSettings.hpp +++ b/include/dialogs/RoomSettings.hpp @@ -5,16 +5,17 @@ #include "Cache.h" -class FlatButton; -class TextField; -class QHBoxLayout; class Avatar; -class QPixmap; -class QLayout; -class QLabel; +class FlatButton; class QComboBox; -class TextField; +class QHBoxLayout; class QLabel; +class QLabel; +class QLayout; +class QPixmap; +class TextField; +class TextField; +class Toggle; template class QSharedPointer; @@ -84,6 +85,7 @@ public: signals: void closing(); + void enableEncryptionError(const QString &msg); protected: void paintEvent(QPaintEvent *event) override; @@ -98,13 +100,15 @@ private: void setupEditButton(); //! Retrieve the current room information from cache. void retrieveRoomInfo(); + void enableEncryption(); //! Whether the user would be able to change the name or the topic of the room. - bool hasEditRights_ = true; + bool hasEditRights_ = true; + bool usesEncryption_ = false; QHBoxLayout *editLayout_; // Button section - FlatButton *saveBtn_; + FlatButton *okBtn_; FlatButton *cancelBtn_; FlatButton *editFieldsBtn_; @@ -116,6 +120,7 @@ private: TopSection *topSection_; QComboBox *accessCombo; + Toggle *encryptionToggle_; }; } // dialogs diff --git a/src/Cache.cc b/src/Cache.cc index 150990b7..48b1fdaf 100644 --- a/src/Cache.cc +++ b/src/Cache.cc @@ -60,7 +60,7 @@ constexpr auto DEVICES_DB("devices"); //! device_id -> device keys constexpr auto DEVICE_KEYS_DB("device_keys"); //! room_ids that have encryption enabled. -// constexpr auto ENCRYPTED_ROOMS_DB("encrypted_rooms"); +constexpr auto ENCRYPTED_ROOMS_DB("encrypted_rooms"); //! MegolmSessionIndex -> pickled OlmInboundGroupSession constexpr auto INBOUND_MEGOLM_SESSIONS_DB("inbound_megolm_sessions"); @@ -184,6 +184,30 @@ Cache::setup() txn.commit(); } +void +Cache::setEncryptedRoom(const std::string &room_id) +{ + log::db()->info("mark room {} as encrypted", room_id); + + auto txn = lmdb::txn::begin(env_); + auto db = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE); + lmdb::dbi_put(txn, db, lmdb::val(room_id), lmdb::val("0")); + txn.commit(); +} + +bool +Cache::isRoomEncrypted(const std::string &room_id) +{ + lmdb::val unused; + + auto txn = lmdb::txn::begin(env_); + auto db = lmdb::dbi::open(txn, ENCRYPTED_ROOMS_DB, MDB_CREATE); + auto res = lmdb::dbi_get(txn, db, lmdb::val(room_id), unused); + txn.commit(); + + return res; +} + // // Device Management // diff --git a/src/ChatPage.cc b/src/ChatPage.cc index a5a6a8c0..3f43831c 100644 --- a/src/ChatPage.cc +++ b/src/ChatPage.cc @@ -245,6 +245,10 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) return; typingRefresher_->stop(); + + if (current_room_.isEmpty()) + return; + http::v2::client()->stop_typing( current_room_.toStdString(), [](mtx::http::RequestErr err) { if (err) { diff --git a/src/dialogs/RoomSettings.cpp b/src/dialogs/RoomSettings.cpp index 2396fc19..a091c8bc 100644 --- a/src/dialogs/RoomSettings.cpp +++ b/src/dialogs/RoomSettings.cpp @@ -1,16 +1,20 @@ #include "Avatar.h" +#include "ChatPage.h" #include "Config.h" #include "FlatButton.h" +#include "Logging.hpp" #include "MatrixClient.h" #include "Painter.h" #include "TextField.h" #include "Theme.h" #include "Utils.h" #include "dialogs/RoomSettings.hpp" +#include "ui/ToggleButton.h" #include #include #include +#include #include #include #include @@ -188,8 +192,8 @@ RoomSettings::RoomSettings(const QString &room_id, QWidget *parent) layout->setSpacing(15); layout->setMargin(20); - saveBtn_ = new FlatButton("SAVE", this); - saveBtn_->setFontSize(conf::btn::fontSize); + okBtn_ = new FlatButton(tr("OK"), this); + okBtn_->setFontSize(conf::btn::fontSize); cancelBtn_ = new FlatButton(tr("CANCEL"), this); cancelBtn_->setFontSize(conf::btn::fontSize); @@ -197,7 +201,7 @@ RoomSettings::RoomSettings(const QString &room_id, QWidget *parent) btnLayout->setSpacing(0); btnLayout->setMargin(0); btnLayout->addStretch(1); - btnLayout->addWidget(saveBtn_); + btnLayout->addWidget(okBtn_); btnLayout->addWidget(cancelBtn_); auto notifOptionLayout_ = new QHBoxLayout; @@ -236,6 +240,61 @@ RoomSettings::RoomSettings(const QString &room_id, QWidget *parent) accessOptionLayout->addWidget(accessLabel); accessOptionLayout->addWidget(accessCombo); + auto encryptionOptionLayout = new QHBoxLayout; + encryptionOptionLayout->setMargin(SettingsMargin); + auto encryptionLabel = new QLabel(tr("Encryption"), this); + encryptionLabel->setStyleSheet("font-size: 15px;"); + encryptionToggle_ = new Toggle(this); + connect(encryptionToggle_, &Toggle::toggled, this, [this](bool isOn) { + if (isOn) + return; + + QFont font; + font.setPixelSize(conf::fontSize); + + QMessageBox msgBox; + msgBox.setIcon(QMessageBox::Question); + msgBox.setFont(font); + msgBox.setWindowTitle(tr("End-to-End Encryption")); + msgBox.setText(tr( + "Encryption is currently experimental and things might break unexpectedly.
" + "Please take note that it can't be disabled afterwards.")); + msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Save); + int ret = msgBox.exec(); + + switch (ret) { + case QMessageBox::Ok: { + encryptionToggle_->setState(false); + encryptionToggle_->setEnabled(false); + enableEncryption(); + break; + } + default: { + encryptionToggle_->setState(true); + encryptionToggle_->setEnabled(true); + break; + } + } + }); + + encryptionOptionLayout->addWidget(encryptionLabel); + encryptionOptionLayout->addWidget(encryptionToggle_, 0, Qt::AlignBottom | Qt::AlignRight); + + // Disable encryption button. + if (usesEncryption_) { + encryptionToggle_->setState(false); + encryptionToggle_->setEnabled(false); + } else { + encryptionToggle_->setState(true); + } + + // Hide encryption option for public rooms. + if (!usesEncryption_ && (info_.join_rule == JoinRule::Public)) { + encryptionToggle_->hide(); + encryptionLabel->hide(); + } + QFont font; font.setPixelSize(18); font.setWeight(70); @@ -255,10 +314,18 @@ RoomSettings::RoomSettings(const QString &room_id, QWidget *parent) layout->addLayout(editLayout_); layout->addLayout(notifOptionLayout_); layout->addLayout(accessOptionLayout); + layout->addLayout(encryptionOptionLayout); layout->addLayout(btnLayout); connect(cancelBtn_, &QPushButton::clicked, this, &RoomSettings::closing); - connect(saveBtn_, &QPushButton::clicked, this, &RoomSettings::saveSettings); + connect(okBtn_, &QPushButton::clicked, this, &RoomSettings::saveSettings); + + connect(this, &RoomSettings::enableEncryptionError, this, [this](const QString &msg) { + encryptionToggle_->setState(true); + encryptionToggle_->setEnabled(true); + + emit ChatPage::instance()->showNotification(msg); + }); } void @@ -308,7 +375,8 @@ void RoomSettings::retrieveRoomInfo() { try { - info_ = cache::client()->singleRoomInfo(room_id_.toStdString()); + usesEncryption_ = cache::client()->isRoomEncrypted(room_id_.toStdString()); + info_ = cache::client()->singleRoomInfo(room_id_.toStdString()); setAvatar(QImage::fromData(cache::client()->image(info_.avatar_url))); } catch (const lmdb::error &e) { qWarning() << "failed to retrieve room info from cache" << room_id_; @@ -339,6 +407,28 @@ RoomSettings::saveSettings() closing(); } +void +RoomSettings::enableEncryption() +{ + const auto room_id = room_id_.toStdString(); + http::v2::client()->enable_encryption( + room_id, [room_id, this](const mtx::responses::EventId &, mtx::http::RequestErr err) { + if (err) { + int status_code = static_cast(err->status_code); + log::net()->warn("failed to enable encryption in room ({}): {} {}", + room_id, + err->matrix_error.error, + status_code); + emit enableEncryptionError( + tr("Failed to enable encryption: %1") + .arg(QString::fromStdString(err->matrix_error.error))); + return; + } + + log::net()->info("enabled encryption on room ({})", room_id); + }); +} + void RoomSettings::paintEvent(QPaintEvent *) { diff --git a/src/timeline/TimelineView.cc b/src/timeline/TimelineView.cc index 9baa1f4a..d004a543 100644 --- a/src/timeline/TimelineView.cc +++ b/src/timeline/TimelineView.cc @@ -284,6 +284,8 @@ TimelineView::parseMessageEvent(const mtx::events::collections::TimelineEvents & auto decrypted = parseEncryptedEvent(mpark::get>(event)); return parseMessageEvent(decrypted, direction); + } else if (mpark::holds_alternative>(event)) { + cache::client()->setEncryptedRoom(room_id_.toStdString()); } return nullptr;