Change indentation to 4 spaces

pull/729/head
Nicolas Werner 3 years ago
parent e118f3882d
commit cfca7157b9
No known key found for this signature in database
GPG Key ID: C8D75E610773F2D9
  1. 6
      .clang-format
  2. 69
      src/AvatarProvider.cpp
  3. 4
      src/AvatarProvider.h
  4. 56
      src/BlurhashProvider.cpp
  5. 48
      src/BlurhashProvider.h
  6. 6424
      src/Cache.cpp
  7. 154
      src/CacheCryptoStructs.h
  8. 86
      src/CacheStructs.h
  9. 1286
      src/Cache_p.h
  10. 443
      src/CallDevices.cpp
  11. 43
      src/CallDevices.h
  12. 918
      src/CallManager.cpp
  13. 151
      src/CallManager.h
  14. 2081
      src/ChatPage.cpp
  15. 281
      src/ChatPage.h
  16. 7
      src/Clipboard.cpp
  17. 12
      src/Clipboard.h
  18. 26
      src/ColorImageProvider.cpp
  19. 8
      src/ColorImageProvider.h
  20. 84
      src/CombinedImagePackModel.cpp
  21. 58
      src/CombinedImagePackModel.h
  22. 6
      src/CompletionModelRoles.h
  23. 188
      src/CompletionProxyModel.cpp
  24. 283
      src/CompletionProxyModel.h
  25. 1279
      src/DeviceVerificationFlow.cpp
  26. 353
      src/DeviceVerificationFlow.h
  27. 489
      src/EventAccessors.cpp
  28. 14
      src/EventAccessors.h
  29. 91
      src/ImagePackListModel.cpp
  30. 46
      src/ImagePackListModel.h
  31. 75
      src/InviteesModel.cpp
  32. 54
      src/InviteesModel.h
  33. 104
      src/JdenticonProvider.cpp
  34. 75
      src/JdenticonProvider.h
  35. 117
      src/Logging.cpp
  36. 795
      src/LoginPage.cpp
  37. 128
      src/LoginPage.h
  38. 540
      src/MainWindow.cpp
  39. 135
      src/MainWindow.h
  40. 30
      src/MatrixClient.cpp
  41. 122
      src/MemberList.cpp
  42. 84
      src/MemberList.h
  43. 420
      src/MxcImageProvider.cpp
  44. 58
      src/MxcImageProvider.h
  45. 2792
      src/Olm.cpp
  46. 32
      src/Olm.h
  47. 147
      src/ReadReceiptsModel.cpp
  48. 74
      src/ReadReceiptsModel.h
  49. 791
      src/RegisterPage.cpp
  50. 118
      src/RegisterPage.h
  51. 280
      src/RoomDirectoryModel.cpp
  52. 94
      src/RoomDirectoryModel.h
  53. 102
      src/RoomsModel.cpp
  54. 38
      src/RoomsModel.h
  55. 64
      src/SSOHandler.cpp
  56. 16
      src/SSOHandler.h
  57. 478
      src/SingleImagePackModel.cpp
  58. 131
      src/SingleImagePackModel.h
  59. 148
      src/TrayIcon.cpp
  60. 28
      src/TrayIcon.h
  61. 2301
      src/UserSettingsPage.cpp
  62. 731
      src/UserSettingsPage.h
  63. 72
      src/UsersModel.cpp
  64. 36
      src/UsersModel.h
  65. 1085
      src/Utils.cpp
  66. 232
      src/Utils.h
  67. 1622
      src/WebRTCSession.cpp
  68. 144
      src/WebRTCSession.h
  69. 98
      src/WelcomePage.cpp
  70. 14
      src/WelcomePage.h
  71. 254
      src/dialogs/CreateRoom.cpp
  72. 28
      src/dialogs/CreateRoom.h
  73. 84
      src/dialogs/FallbackAuth.cpp
  74. 14
      src/dialogs/FallbackAuth.h
  75. 107
      src/dialogs/ImageOverlay.cpp
  76. 24
      src/dialogs/ImageOverlay.h
  77. 66
      src/dialogs/JoinRoom.cpp
  78. 16
      src/dialogs/JoinRoom.h
  79. 54
      src/dialogs/LeaveRoom.cpp
  80. 10
      src/dialogs/LeaveRoom.h
  81. 72
      src/dialogs/Logout.cpp
  82. 10
      src/dialogs/Logout.h
  83. 283
      src/dialogs/PreviewUploadOverlay.cpp
  84. 40
      src/dialogs/PreviewUploadOverlay.h
  85. 80
      src/dialogs/ReCaptcha.cpp
  86. 14
      src/dialogs/ReCaptcha.h
  87. 69
      src/emoji/EmojiModel.cpp
  88. 26
      src/emoji/EmojiModel.h
  89. 4
      src/emoji/MacHelper.h
  90. 46
      src/emoji/Provider.h
  91. 403
      src/main.cpp
  92. 48
      src/notifications/Manager.cpp
  93. 82
      src/notifications/Manager.h
  94. 357
      src/notifications/ManagerLinux.cpp
  95. 70
      src/notifications/ManagerMac.cpp
  96. 100
      src/notifications/ManagerWin.cpp
  97. 373
      src/timeline/CommunitiesModel.cpp
  98. 102
      src/timeline/CommunitiesModel.h
  99. 115
      src/timeline/DelegateChooser.cpp
  100. 94
      src/timeline/DelegateChooser.h
  101. Some files were not shown because too many files have changed in this diff Show More

@ -1,14 +1,14 @@
---
Language: Cpp
Standard: Cpp11
AccessModifierOffset: -8
Standard: c++17
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: true
AllowShortFunctionsOnASingleLine: true
BasedOnStyle: Mozilla
ColumnLimit: 100
IndentCaseLabels: false
IndentWidth: 8
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: false
PointerAlignment: Right
Cpp11BracedListStyle: true

@ -22,45 +22,44 @@ namespace AvatarProvider {
void
resolve(QString avatarUrl, int size, QObject *receiver, AvatarCallback callback)
{
const auto cacheKey = QString("%1_size_%2").arg(avatarUrl).arg(size);
const auto cacheKey = QString("%1_size_%2").arg(avatarUrl).arg(size);
QPixmap pixmap;
if (avatarUrl.isEmpty()) {
callback(pixmap);
return;
}
QPixmap pixmap;
if (avatarUrl.isEmpty()) {
callback(pixmap);
return;
}
if (avatar_cache.find(cacheKey, &pixmap)) {
callback(pixmap);
return;
}
if (avatar_cache.find(cacheKey, &pixmap)) {
callback(pixmap);
return;
}
MxcImageProvider::download(avatarUrl.remove(QStringLiteral("mxc://")),
QSize(size, size),
[callback, cacheKey, recv = QPointer<QObject>(receiver)](
QString, QSize, QImage img, QString) {
if (!recv)
return;
MxcImageProvider::download(avatarUrl.remove(QStringLiteral("mxc://")),
QSize(size, size),
[callback, cacheKey, recv = QPointer<QObject>(receiver)](
QString, QSize, QImage img, QString) {
if (!recv)
return;
auto proxy = std::make_shared<AvatarProxy>();
QObject::connect(proxy.get(),
&AvatarProxy::avatarDownloaded,
recv,
[callback, cacheKey](QPixmap pm) {
if (!pm.isNull())
avatar_cache.insert(
cacheKey, pm);
callback(pm);
});
auto proxy = std::make_shared<AvatarProxy>();
QObject::connect(proxy.get(),
&AvatarProxy::avatarDownloaded,
recv,
[callback, cacheKey](QPixmap pm) {
if (!pm.isNull())
avatar_cache.insert(cacheKey, pm);
callback(pm);
});
if (img.isNull()) {
emit proxy->avatarDownloaded(QPixmap{});
return;
}
if (img.isNull()) {
emit proxy->avatarDownloaded(QPixmap{});
return;
}
auto pm = QPixmap::fromImage(std::move(img));
emit proxy->avatarDownloaded(pm);
});
auto pm = QPixmap::fromImage(std::move(img));
emit proxy->avatarDownloaded(pm);
});
}
void
@ -70,8 +69,8 @@ resolve(const QString &room_id,
QObject *receiver,
AvatarCallback callback)
{
auto avatarUrl = cache::avatarUrl(room_id, user_id);
auto avatarUrl = cache::avatarUrl(room_id, user_id);
resolve(std::move(avatarUrl), size, receiver, callback);
resolve(std::move(avatarUrl), size, receiver, callback);
}
}

@ -12,10 +12,10 @@ using AvatarCallback = std::function<void(QPixmap)>;
class AvatarProxy : public QObject
{
Q_OBJECT
Q_OBJECT
signals:
void avatarDownloaded(QPixmap pm);
void avatarDownloaded(QPixmap pm);
};
namespace AvatarProvider {

@ -13,33 +13,33 @@
void
BlurhashResponse::run()
{
if (m_requestedSize.width() < 0 || m_requestedSize.height() < 0) {
m_error = QStringLiteral("Blurhash needs size request");
emit finished();
return;
}
if (m_requestedSize.width() == 0 || m_requestedSize.height() == 0) {
m_image = QImage(m_requestedSize, QImage::Format_RGB32);
m_image.fill(QColor(0, 0, 0));
emit finished();
return;
}
auto decoded = blurhash::decode(QUrl::fromPercentEncoding(m_id.toUtf8()).toStdString(),
m_requestedSize.width(),
m_requestedSize.height());
if (decoded.image.empty()) {
m_error = QStringLiteral("Failed decode!");
emit finished();
return;
}
QImage image(decoded.image.data(),
(int)decoded.width,
(int)decoded.height,
(int)decoded.width * 3,
QImage::Format_RGB888);
m_image = image.copy();
if (m_requestedSize.width() < 0 || m_requestedSize.height() < 0) {
m_error = QStringLiteral("Blurhash needs size request");
emit finished();
return;
}
if (m_requestedSize.width() == 0 || m_requestedSize.height() == 0) {
m_image = QImage(m_requestedSize, QImage::Format_RGB32);
m_image.fill(QColor(0, 0, 0));
emit finished();
return;
}
auto decoded = blurhash::decode(QUrl::fromPercentEncoding(m_id.toUtf8()).toStdString(),
m_requestedSize.width(),
m_requestedSize.height());
if (decoded.image.empty()) {
m_error = QStringLiteral("Failed decode!");
emit finished();
return;
}
QImage image(decoded.image.data(),
(int)decoded.width,
(int)decoded.height,
(int)decoded.width * 3,
QImage::Format_RGB888);
m_image = image.copy();
emit finished();
}

@ -15,41 +15,41 @@ class BlurhashResponse
, public QRunnable
{
public:
BlurhashResponse(const QString &id, const QSize &requestedSize)
BlurhashResponse(const QString &id, const QSize &requestedSize)
: m_id(id)
, m_requestedSize(requestedSize)
{
setAutoDelete(false);
}
: m_id(id)
, m_requestedSize(requestedSize)
{
setAutoDelete(false);
}
QQuickTextureFactory *textureFactory() const override
{
return QQuickTextureFactory::textureFactoryForImage(m_image);
}
QString errorString() const override { return m_error; }
QQuickTextureFactory *textureFactory() const override
{
return QQuickTextureFactory::textureFactoryForImage(m_image);
}
QString errorString() const override { return m_error; }
void run() override;
void run() override;
QString m_id, m_error;
QSize m_requestedSize;
QImage m_image;
QString m_id, m_error;
QSize m_requestedSize;
QImage m_image;
};
class BlurhashProvider
: public QObject
, public QQuickAsyncImageProvider
{
Q_OBJECT
Q_OBJECT
public slots:
QQuickImageResponse *requestImageResponse(const QString &id,
const QSize &requestedSize) override
{
BlurhashResponse *response = new BlurhashResponse(id, requestedSize);
pool.start(response);
return response;
}
QQuickImageResponse *requestImageResponse(const QString &id,
const QSize &requestedSize) override
{
BlurhashResponse *response = new BlurhashResponse(id, requestedSize);
pool.start(response);
return response;
}
private:
QThreadPool pool;
QThreadPool pool;
};

File diff suppressed because it is too large Load Diff

@ -19,48 +19,48 @@ Q_NAMESPACE
//! How much a participant is trusted.
enum Trust
{
Unverified, //! Device unverified or master key changed.
TOFU, //! Device is signed by the sender, but the user is not verified, but they never
//! changed the master key.
Verified, //! User was verified and has crosssigned this device or device is verified.
Unverified, //! Device unverified or master key changed.
TOFU, //! Device is signed by the sender, but the user is not verified, but they never
//! changed the master key.
Verified, //! User was verified and has crosssigned this device or device is verified.
};
Q_ENUM_NS(Trust)
}
struct DeviceKeysToMsgIndex
{
// map from device key to message_index
// Using the device id is safe because we check for reuse on device list updates
// Using the device id makes our logic much easier to read.
std::map<std::string, uint64_t> deviceids;
// map from device key to message_index
// Using the device id is safe because we check for reuse on device list updates
// Using the device id makes our logic much easier to read.
std::map<std::string, uint64_t> deviceids;
};
struct SharedWithUsers
{
// userid to keys
std::map<std::string, DeviceKeysToMsgIndex> keys;
// userid to keys
std::map<std::string, DeviceKeysToMsgIndex> keys;
};
// Extra information associated with an outbound megolm session.
struct GroupSessionData
{
uint64_t message_index = 0;
uint64_t timestamp = 0;
uint64_t message_index = 0;
uint64_t timestamp = 0;
// If we got the session via key sharing or forwarding, we can usually trust it.
// If it came from asymmetric key backup, it is not trusted.
// TODO(Nico): What about forwards? They might come from key backup?
bool trusted = true;
// If we got the session via key sharing or forwarding, we can usually trust it.
// If it came from asymmetric key backup, it is not trusted.
// TODO(Nico): What about forwards? They might come from key backup?
bool trusted = true;
std::string sender_claimed_ed25519_key;
std::vector<std::string> forwarding_curve25519_key_chain;
std::string sender_claimed_ed25519_key;
std::vector<std::string> forwarding_curve25519_key_chain;
//! map from index to event_id to check for replay attacks
std::map<uint32_t, std::string> indices;
//! map from index to event_id to check for replay attacks
std::map<uint32_t, std::string> indices;
// who has access to this session.
// Rotate, when a user leaves the room and share, when a user gets added.
SharedWithUsers currently;
// who has access to this session.
// Rotate, when a user leaves the room and share, when a user gets added.
SharedWithUsers currently;
};
void
@ -70,14 +70,14 @@ from_json(const nlohmann::json &obj, GroupSessionData &msg);
struct OutboundGroupSessionDataRef
{
mtx::crypto::OutboundGroupSessionPtr session;
GroupSessionData data;
mtx::crypto::OutboundGroupSessionPtr session;
GroupSessionData data;
};
struct DevicePublicKeys
{
std::string ed25519;
std::string curve25519;
std::string ed25519;
std::string curve25519;
};
void
@ -88,19 +88,19 @@ from_json(const nlohmann::json &obj, DevicePublicKeys &msg);
//! Represents a unique megolm session identifier.
struct MegolmSessionIndex
{
MegolmSessionIndex() = default;
MegolmSessionIndex(std::string room_id_, const mtx::events::msg::Encrypted &e)
: room_id(std::move(room_id_))
, session_id(e.session_id)
, sender_key(e.sender_key)
{}
//! The room in which this session exists.
std::string room_id;
//! The session_id of the megolm session.
std::string session_id;
//! The curve25519 public key of the sender.
std::string sender_key;
MegolmSessionIndex() = default;
MegolmSessionIndex(std::string room_id_, const mtx::events::msg::Encrypted &e)
: room_id(std::move(room_id_))
, session_id(e.session_id)
, sender_key(e.sender_key)
{}
//! The room in which this session exists.
std::string room_id;
//! The session_id of the megolm session.
std::string session_id;
//! The curve25519 public key of the sender.
std::string sender_key;
};
void
@ -110,8 +110,8 @@ from_json(const nlohmann::json &obj, MegolmSessionIndex &msg);
struct StoredOlmSession
{
std::uint64_t last_message_ts = 0;
std::string pickled_session;
std::uint64_t last_message_ts = 0;
std::string pickled_session;
};
void
to_json(nlohmann::json &obj, const StoredOlmSession &msg);
@ -121,43 +121,43 @@ from_json(const nlohmann::json &obj, StoredOlmSession &msg);
//! Verification status of a single user
struct VerificationStatus
{
//! True, if the users master key is verified
crypto::Trust user_verified = crypto::Trust::Unverified;
//! List of all devices marked as verified
std::set<std::string> verified_devices;
//! Map from sender key/curve25519 to trust status
std::map<std::string, crypto::Trust> verified_device_keys;
//! Count of unverified devices
int unverified_device_count = 0;
// if the keys are not in cache
bool no_keys = false;
//! True, if the users master key is verified
crypto::Trust user_verified = crypto::Trust::Unverified;
//! List of all devices marked as verified
std::set<std::string> verified_devices;
//! Map from sender key/curve25519 to trust status
std::map<std::string, crypto::Trust> verified_device_keys;
//! Count of unverified devices
int unverified_device_count = 0;
// if the keys are not in cache
bool no_keys = false;
};
//! In memory cache of verification status
struct VerificationStorage
{
//! mapping of user to verification status
std::map<std::string, VerificationStatus> status;
std::mutex verification_storage_mtx;
//! mapping of user to verification status
std::map<std::string, VerificationStatus> status;
std::mutex verification_storage_mtx;
};
// this will store the keys of the user with whom a encrypted room is shared with
struct UserKeyCache
{
//! Device id to device keys
std::map<std::string, mtx::crypto::DeviceKeys> device_keys;
//! cross signing keys
mtx::crypto::CrossSigningKeys master_keys, user_signing_keys, self_signing_keys;
//! Sync token when nheko last fetched the keys
std::string updated_at;
//! Sync token when the keys last changed. updated != last_changed means they are outdated.
std::string last_changed;
//! if the master key has ever changed
bool master_key_changed = false;
//! Device keys that were already used at least once
std::set<std::string> seen_device_keys;
//! Device ids that were already used at least once
std::set<std::string> seen_device_ids;
//! Device id to device keys
std::map<std::string, mtx::crypto::DeviceKeys> device_keys;
//! cross signing keys
mtx::crypto::CrossSigningKeys master_keys, user_signing_keys, self_signing_keys;
//! Sync token when nheko last fetched the keys
std::string updated_at;
//! Sync token when the keys last changed. updated != last_changed means they are outdated.
std::string last_changed;
//! if the master key has ever changed
bool master_key_changed = false;
//! Device keys that were already used at least once
std::set<std::string> seen_device_keys;
//! Device ids that were already used at least once
std::set<std::string> seen_device_ids;
};
void
@ -169,10 +169,10 @@ from_json(const nlohmann::json &j, UserKeyCache &info);
// UserKeyCache stores only keys of users with which encrypted room is shared
struct VerificationCache
{
//! list of verified device_ids with device-verification
std::set<std::string> device_verified;
//! list of devices the user blocks
std::set<std::string> device_blocked;
//! list of verified device_ids with device-verification
std::set<std::string> device_verified;
//! list of devices the user blocks
std::set<std::string> device_blocked;
};
void
@ -182,10 +182,10 @@ from_json(const nlohmann::json &j, VerificationCache &info);
struct OnlineBackupVersion
{
//! the version of the online backup currently enabled
std::string version;
//! the algorithm used by the backup
std::string algorithm;
//! the version of the online backup currently enabled
std::string version;
//! the algorithm used by the backup
std::string algorithm;
};
void

@ -16,23 +16,23 @@
namespace cache {
enum class CacheVersion : int
{
Older = -1,
Current = 0,
Newer = 1,
Older = -1,
Current = 0,
Newer = 1,
};
}
struct RoomMember
{
QString user_id;
QString display_name;
QString user_id;
QString display_name;
};
//! Used to uniquely identify a list of read receipts.
struct ReadReceiptKey
{
std::string event_id;
std::string room_id;
std::string event_id;
std::string room_id;
};
void
@ -43,49 +43,49 @@ from_json(const nlohmann::json &j, ReadReceiptKey &key);
struct DescInfo
{
QString event_id;
QString userid;
QString body;
QString descriptiveTime;
uint64_t timestamp;
QDateTime datetime;
QString event_id;
QString userid;
QString body;
QString descriptiveTime;
uint64_t timestamp;
QDateTime datetime;
};
inline bool
operator==(const DescInfo &a, const DescInfo &b)
{
return std::tie(a.timestamp, a.event_id, a.userid, a.body, a.descriptiveTime) ==
std::tie(b.timestamp, b.event_id, b.userid, b.body, b.descriptiveTime);
return std::tie(a.timestamp, a.event_id, a.userid, a.body, a.descriptiveTime) ==
std::tie(b.timestamp, b.event_id, b.userid, b.body, b.descriptiveTime);
}
inline bool
operator!=(const DescInfo &a, const DescInfo &b)
{
return std::tie(a.timestamp, a.event_id, a.userid, a.body, a.descriptiveTime) !=
std::tie(b.timestamp, b.event_id, b.userid, b.body, b.descriptiveTime);
return std::tie(a.timestamp, a.event_id, a.userid, a.body, a.descriptiveTime) !=
std::tie(b.timestamp, b.event_id, b.userid, b.body, b.descriptiveTime);
}
//! UI info associated with a room.
struct RoomInfo
{
//! The calculated name of the room.
std::string name;
//! The topic of the room.
std::string topic;
//! The calculated avatar url of the room.
std::string avatar_url;
//! The calculated version of this room set at creation time.
std::string version;
//! Whether or not the room is an invite.
bool is_invite = false;
//! Wheter or not the room is a space
bool is_space = false;
//! Total number of members in the room.
size_t member_count = 0;
//! Who can access to the room.
mtx::events::state::JoinRule join_rule = mtx::events::state::JoinRule::Public;
bool guest_access = false;
//! The list of tags associated with this room
std::vector<std::string> tags;
//! The calculated name of the room.
std::string name;
//! The topic of the room.
std::string topic;
//! The calculated avatar url of the room.
std::string avatar_url;
//! The calculated version of this room set at creation time.
std::string version;
//! Whether or not the room is an invite.
bool is_invite = false;
//! Wheter or not the room is a space
bool is_space = false;
//! Total number of members in the room.
size_t member_count = 0;
//! Who can access to the room.
mtx::events::state::JoinRule join_rule = mtx::events::state::JoinRule::Public;
bool guest_access = false;
//! The list of tags associated with this room
std::vector<std::string> tags;
};
void
@ -96,8 +96,8 @@ from_json(const nlohmann::json &j, RoomInfo &info);
//! Basic information per member.
struct MemberInfo
{
std::string name;
std::string avatar_url;
std::string name;
std::string avatar_url;
};
void
@ -107,13 +107,13 @@ from_json(const nlohmann::json &j, MemberInfo &info);
struct RoomSearchResult
{
std::string room_id;
RoomInfo info;
std::string room_id;
RoomInfo info;
};
struct ImagePackInfo
{
mtx::events::msc2545::ImagePack pack;
std::string source_room;
std::string state_key;
mtx::events::msc2545::ImagePack pack;
std::string source_room;
std::string state_key;
};

File diff suppressed because it is too large Load Diff

@ -27,20 +27,20 @@ namespace {
struct AudioSource
{
std::string name;
GstDevice *device;
std::string name;
GstDevice *device;
};
struct VideoSource
{
struct Caps
{
std::string resolution;
std::vector<std::string> frameRates;
};
std::string name;
GstDevice *device;
std::vector<Caps> caps;
struct Caps
{
std::string resolution;
std::vector<std::string> frameRates;
};
std::string name;
GstDevice *device;
std::vector<Caps> caps;
};
std::vector<AudioSource> audioSources_;
@ -50,315 +50,304 @@ using FrameRate = std::pair<int, int>;
std::optional<FrameRate>
getFrameRate(const GValue *value)
{
if (GST_VALUE_HOLDS_FRACTION(value)) {
gint num = gst_value_get_fraction_numerator(value);
gint den = gst_value_get_fraction_denominator(value);
return FrameRate{num, den};
}
return std::nullopt;
if (GST_VALUE_HOLDS_FRACTION(value)) {
gint num = gst_value_get_fraction_numerator(value);
gint den = gst_value_get_fraction_denominator(value);
return FrameRate{num, den};
}
return std::nullopt;
}
void
addFrameRate(std::vector<std::string> &rates, const FrameRate &rate)
{
constexpr double minimumFrameRate = 15.0;
if (static_cast<double>(rate.first) / rate.second >= minimumFrameRate)
rates.push_back(std::to_string(rate.first) + "/" + std::to_string(rate.second));
constexpr double minimumFrameRate = 15.0;
if (static_cast<double>(rate.first) / rate.second >= minimumFrameRate)
rates.push_back(std::to_string(rate.first) + "/" + std::to_string(rate.second));
}
void
setDefaultDevice(bool isVideo)
{
auto settings = ChatPage::instance()->userSettings();
if (isVideo && settings->camera().isEmpty()) {
const VideoSource &camera = videoSources_.front();
settings->setCamera(QString::fromStdString(camera.name));
settings->setCameraResolution(
QString::fromStdString(camera.caps.front().resolution));
settings->setCameraFrameRate(
QString::fromStdString(camera.caps.front().frameRates.front()));
} else if (!isVideo && settings->microphone().isEmpty()) {
settings->setMicrophone(QString::fromStdString(audioSources_.front().name));
}
auto settings = ChatPage::instance()->userSettings();
if (isVideo && settings->camera().isEmpty()) {
const VideoSource &camera = videoSources_.front();
settings->setCamera(QString::fromStdString(camera.name));
settings->setCameraResolution(QString::fromStdString(camera.caps.front().resolution));
settings->setCameraFrameRate(
QString::fromStdString(camera.caps.front().frameRates.front()));
} else if (!isVideo && settings->microphone().isEmpty()) {
settings->setMicrophone(QString::fromStdString(audioSources_.front().name));
}
}
void
addDevice(GstDevice *device)
{
if (!device)
return;
gchar *name = gst_device_get_display_name(device);
gchar *type = gst_device_get_device_class(device);
bool isVideo = !std::strncmp(type, "Video", 5);
g_free(type);
nhlog::ui()->debug("WebRTC: {} device added: {}", isVideo ? "video" : "audio", name);
if (!isVideo) {
audioSources_.push_back({name, device});
g_free(name);
setDefaultDevice(false);
return;
}
GstCaps *gstcaps = gst_device_get_caps(device);
if (!gstcaps) {
nhlog::ui()->debug("WebRTC: unable to get caps for {}", name);
g_free(name);
return;
}
if (!device)
return;
gchar *name = gst_device_get_display_name(device);
gchar *type = gst_device_get_device_class(device);
bool isVideo = !std::strncmp(type, "Video", 5);
g_free(type);
nhlog::ui()->debug("WebRTC: {} device added: {}", isVideo ? "video" : "audio", name);
if (!isVideo) {
audioSources_.push_back({name, device});
g_free(name);
setDefaultDevice(false);
return;
}
VideoSource source{name, device, {}};
GstCaps *gstcaps = gst_device_get_caps(device);
if (!gstcaps) {
nhlog::ui()->debug("WebRTC: unable to get caps for {}", name);
g_free(name);
guint nCaps = gst_caps_get_size(gstcaps);
for (guint i = 0; i < nCaps; ++i) {
GstStructure *structure = gst_caps_get_structure(gstcaps, i);
const gchar *struct_name = gst_structure_get_name(structure);
if (!std::strcmp(struct_name, "video/x-raw")) {
gint widthpx, heightpx;
if (gst_structure_get(structure,
"width",
G_TYPE_INT,
&widthpx,
"height",
G_TYPE_INT,
&heightpx,
nullptr)) {
VideoSource::Caps caps;
caps.resolution =
std::to_string(widthpx) + "x" + std::to_string(heightpx);
const GValue *value =
gst_structure_get_value(structure, "framerate");
if (auto fr = getFrameRate(value); fr)
addFrameRate(caps.frameRates, *fr);
else if (GST_VALUE_HOLDS_FRACTION_RANGE(value)) {
addFrameRate(
caps.frameRates,
*getFrameRate(gst_value_get_fraction_range_min(value)));
addFrameRate(
caps.frameRates,
*getFrameRate(gst_value_get_fraction_range_max(value)));
} else if (GST_VALUE_HOLDS_LIST(value)) {
guint nRates = gst_value_list_get_size(value);
for (guint j = 0; j < nRates; ++j) {
const GValue *rate =
gst_value_list_get_value(value, j);
if (auto frate = getFrameRate(rate); frate)
addFrameRate(caps.frameRates, *frate);
}
}
if (!caps.frameRates.empty())
source.caps.push_back(std::move(caps));
}
return;
}
VideoSource source{name, device, {}};
g_free(name);
guint nCaps = gst_caps_get_size(gstcaps);
for (guint i = 0; i < nCaps; ++i) {
GstStructure *structure = gst_caps_get_structure(gstcaps, i);
const gchar *struct_name = gst_structure_get_name(structure);
if (!std::strcmp(struct_name, "video/x-raw")) {
gint widthpx, heightpx;
if (gst_structure_get(structure,
"width",
G_TYPE_INT,
&widthpx,
"height",
G_TYPE_INT,
&heightpx,
nullptr)) {
VideoSource::Caps caps;
caps.resolution = std::to_string(widthpx) + "x" + std::to_string(heightpx);
const GValue *value = gst_structure_get_value(structure, "framerate");
if (auto fr = getFrameRate(value); fr)
addFrameRate(caps.frameRates, *fr);
else if (GST_VALUE_HOLDS_FRACTION_RANGE(value)) {
addFrameRate(caps.frameRates,
*getFrameRate(gst_value_get_fraction_range_min(value)));
addFrameRate(caps.frameRates,
*getFrameRate(gst_value_get_fraction_range_max(value)));
} else if (GST_VALUE_HOLDS_LIST(value)) {
guint nRates = gst_value_list_get_size(value);
for (guint j = 0; j < nRates; ++j) {
const GValue *rate = gst_value_list_get_value(value, j);
if (auto frate = getFrameRate(rate); frate)
addFrameRate(caps.frameRates, *frate);
}
}
if (!caps.frameRates.empty())
source.caps.push_back(std::move(caps));
}
}
gst_caps_unref(gstcaps);
videoSources_.push_back(std::move(source));
setDefaultDevice(true);
}
gst_caps_unref(gstcaps);
videoSources_.push_back(std::move(source));
setDefaultDevice(true);
}
template<typename T>
bool
removeDevice(T &sources, GstDevice *device, bool changed)
{
if (auto it = std::find_if(sources.begin(),
sources.end(),
[device](const auto &s) { return s.device == device; });
it != sources.end()) {
nhlog::ui()->debug(std::string("WebRTC: device ") +
(changed ? "changed: " : "removed: ") + "{}",
it->name);
gst_object_unref(device);
sources.erase(it);
return true;
}
return false;
if (auto it = std::find_if(
sources.begin(), sources.end(), [device](const auto &s) { return s.device == device; });
it != sources.end()) {
nhlog::ui()->debug(
std::string("WebRTC: device ") + (changed ? "changed: " : "removed: ") + "{}", it->name);
gst_object_unref(device);
sources.erase(it);
return true;
}
return false;
}
void
removeDevice(GstDevice *device, bool changed)
{
if (device) {
if (removeDevice(audioSources_, device, changed) ||
removeDevice(videoSources_, device, changed))
return;
}
if (device) {
if (removeDevice(audioSources_, device, changed) ||
removeDevice(videoSources_, device, changed))
return;
}
}
gboolean
newBusMessage(GstBus *bus G_GNUC_UNUSED, GstMessage *msg, gpointer user_data G_GNUC_UNUSED)
{
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_DEVICE_ADDED: {
GstDevice *device;
gst_message_parse_device_added(msg, &device);
addDevice(device);
emit CallDevices::instance().devicesChanged();
break;
}
case GST_MESSAGE_DEVICE_REMOVED: {
GstDevice *device;
gst_message_parse_device_removed(msg, &device);
removeDevice(device, false);
emit CallDevices::instance().devicesChanged();
break;
}
case GST_MESSAGE_DEVICE_CHANGED: {
GstDevice *device;
GstDevice *oldDevice;
gst_message_parse_device_changed(msg, &device, &oldDevice);
removeDevice(oldDevice, true);
addDevice(device);
break;
}
default:
break;
}
return TRUE;
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_DEVICE_ADDED: {
GstDevice *device;
gst_message_parse_device_added(msg, &device);
addDevice(device);
emit CallDevices::instance().devicesChanged();
break;
}
case GST_MESSAGE_DEVICE_REMOVED: {
GstDevice *device;
gst_message_parse_device_removed(msg, &device);
removeDevice(device, false);
emit CallDevices::instance().devicesChanged();
break;
}
case GST_MESSAGE_DEVICE_CHANGED: {
GstDevice *device;
GstDevice *oldDevice;
gst_message_parse_device_changed(msg, &device, &oldDevice);
removeDevice(oldDevice, true);
addDevice(device);
break;
}
default:
break;
}
return TRUE;
}
template<typename T>
std::vector<std::string>
deviceNames(T &sources, const std::string &defaultDevice)
{
std::vector<std::string> ret;
ret.reserve(sources.size());
for (const auto &s : sources)
ret.push_back(s.name);
std::vector<std::string> ret;
ret.reserve(sources.size());
for (const auto &s : sources)
ret.push_back(s.name);
// move default device to top of the list
if (auto it = std::find(ret.begin(), ret.end(), defaultDevice); it != ret.end())
std::swap(ret.front(), *it);
// move default device to top of the list
if (auto it = std::find(ret.begin(), ret.end(), defaultDevice); it != ret.end())
std::swap(ret.front(), *it);
return ret;
return ret;
}
std::optional<VideoSource>
getVideoSource(const std::string &cameraName)
{
if (auto it = std::find_if(videoSources_.cbegin(),
videoSources_.cend(),
[&cameraName](const auto &s) { return s.name == cameraName; });
it != videoSources_.cend()) {
return *it;
}
return std::nullopt;
if (auto it = std::find_if(videoSources_.cbegin(),
videoSources_.cend(),
[&cameraName](const auto &s) { return s.name == cameraName; });
it != videoSources_.cend()) {
return *it;
}
return std::nullopt;
}
std::pair<int, int>
tokenise(std::string_view str, char delim)
{
std::pair<int, int> ret;
ret.first = std::atoi(str.data());
auto pos = str.find_first_of(delim);
ret.second = std::atoi(str.data() + pos + 1);
return ret;
std::pair<int, int> ret;
ret.first = std::atoi(str.data());
auto pos = str.find_first_of(delim);
ret.second = std::atoi(str.data() + pos + 1);
return ret;
}
}
void
CallDevices::init()
{
static GstDeviceMonitor *monitor = nullptr;
if (!monitor) {
monitor = gst_device_monitor_new();
GstCaps *caps = gst_caps_new_empty_simple("audio/x-raw");
gst_device_monitor_add_filter(monitor, "Audio/Source", caps);
gst_device_monitor_add_filter(monitor, "Audio/Duplex", caps);
gst_caps_unref(caps);
caps = gst_caps_new_empty_simple("video/x-raw");
gst_device_monitor_add_filter(monitor, "Video/Source", caps);
gst_device_monitor_add_filter(monitor, "Video/Duplex", caps);
gst_caps_unref(caps);
GstBus *bus = gst_device_monitor_get_bus(monitor);
gst_bus_add_watch(bus, newBusMessage, nullptr);
gst_object_unref(bus);
if (!gst_device_monitor_start(monitor)) {
nhlog::ui()->error("WebRTC: failed to start device monitor");
return;
}
static GstDeviceMonitor *monitor = nullptr;
if (!monitor) {
monitor = gst_device_monitor_new();
GstCaps *caps = gst_caps_new_empty_simple("audio/x-raw");
gst_device_monitor_add_filter(monitor, "Audio/Source", caps);
gst_device_monitor_add_filter(monitor, "Audio/Duplex", caps);
gst_caps_unref(caps);
caps = gst_caps_new_empty_simple("video/x-raw");
gst_device_monitor_add_filter(monitor, "Video/Source", caps);
gst_device_monitor_add_filter(monitor, "Video/Duplex", caps);
gst_caps_unref(caps);
GstBus *bus = gst_device_monitor_get_bus(monitor);
gst_bus_add_watch(bus, newBusMessage, nullptr);
gst_object_unref(bus);
if (!gst_device_monitor_start(monitor)) {
nhlog::ui()->error("WebRTC: failed to start device monitor");
return;
}
}
}
bool
CallDevices::haveMic() const
{
return !audioSources_.empty();
return !audioSources_.empty();
}
bool
CallDevices::haveCamera() const
{
return !videoSources_.empty();
return !videoSources_.empty();
}
std::vector<std::string>
CallDevices::names(bool isVideo, const std::string &defaultDevice) const
{
return isVideo ? deviceNames(videoSources_, defaultDevice)
: deviceNames(audioSources_, defaultDevice);
return isVideo ? deviceNames(videoSources_, defaultDevice)
: deviceNames(audioSources_, defaultDevice);
}
std::vector<std::string>
CallDevices::resolutions(const std::string &cameraName) const
{
std::vector<std::string> ret;
if (auto s = getVideoSource(cameraName); s) {
ret.reserve(s->caps.size());
for (const auto &c : s->caps)
ret.push_back(c.resolution);
}
return ret;
std::vector<std::string> ret;
if (auto s = getVideoSource(cameraName); s) {
ret.reserve(s->caps.size());
for (const auto &c : s->caps)
ret.push_back(c.resolution);
}
return ret;
}
std::vector<std::string>
CallDevices::frameRates(const std::string &cameraName, const std::string &resolution) const
{
if (auto s = getVideoSource(cameraName); s) {
if (auto it =
std::find_if(s->caps.cbegin(),
if (auto s = getVideoSource(cameraName); s) {
if (auto it = std::find_if(s->caps.cbegin(),
s->caps.cend(),
[&](const auto &c) { return c.resolution == resolution; });
it != s->caps.cend())
return it->frameRates;
}
return {};
it != s->caps.cend())
return it->frameRates;
}
return {};
}
GstDevice *
CallDevices::audioDevice() const
{
std::string name = ChatPage::instance()->userSettings()->microphone().toStdString();
if (auto it = std::find_if(audioSources_.cbegin(),
audioSources_.cend(),
[&name](const auto &s) { return s.name == name; });
it != audioSources_.cend()) {
nhlog::ui()->debug("WebRTC: microphone: {}", name);
return it->device;
} else {
nhlog::ui()->error("WebRTC: unknown microphone: {}", name);
return nullptr;
}
std::string name = ChatPage::instance()->userSettings()->microphone().toStdString();
if (auto it = std::find_if(audioSources_.cbegin(),
audioSources_.cend(),
[&name](const auto &s) { return s.name == name; });
it != audioSources_.cend()) {
nhlog::ui()->debug("WebRTC: microphone: {}", name);
return it->device;
} else {
nhlog::ui()->error("WebRTC: unknown microphone: {}", name);
return nullptr;
}
}
GstDevice *
CallDevices::videoDevice(std::pair<int, int> &resolution, std::pair<int, int> &frameRate) const
{
auto settings = ChatPage::instance()->userSettings();
std::string name = settings->camera().toStdString();
if (auto s = getVideoSource(name); s) {
nhlog::ui()->debug("WebRTC: camera: {}", name);
resolution = tokenise(settings->cameraResolution().toStdString(), 'x');
frameRate = tokenise(settings->cameraFrameRate().toStdString(), '/');
nhlog::ui()->debug(
"WebRTC: camera resolution: {}x{}", resolution.first, resolution.second);
nhlog::ui()->debug(
"WebRTC: camera frame rate: {}/{}", frameRate.first, frameRate.second);
return s->device;
} else {
nhlog::ui()->error("WebRTC: unknown camera: {}", name);
return nullptr;
}
auto settings = ChatPage::instance()->userSettings();
std::string name = settings->camera().toStdString();
if (auto s = getVideoSource(name); s) {
nhlog::ui()->debug("WebRTC: camera: {}", name);
resolution = tokenise(settings->cameraResolution().toStdString(), 'x');
frameRate = tokenise(settings->cameraFrameRate().toStdString(), '/');
nhlog::ui()->debug("WebRTC: camera resolution: {}x{}", resolution.first, resolution.second);
nhlog::ui()->debug("WebRTC: camera frame rate: {}/{}", frameRate.first, frameRate.second);
return s->device;
} else {
nhlog::ui()->error("WebRTC: unknown camera: {}", name);
return nullptr;
}
}
#else
@ -366,31 +355,31 @@ CallDevices::videoDevice(std::pair<int, int> &resolution, std::pair<int, int> &f
bool
CallDevices::haveMic() const
{
return false;
return false;
}
bool
CallDevices::haveCamera() const
{
return false;
return false;
}
std::vector<std::string>
CallDevices::names(bool, const std::string &) const
{
return {};
return {};
}
std::vector<std::string>
CallDevices::resolutions(const std::string &) const
{
return {};
return {};
}
std::vector<std::string>
CallDevices::frameRates(const std::string &, const std::string &) const
{
return {};
return {};
}
#endif

@ -14,35 +14,34 @@ typedef struct _GstDevice GstDevice;
class CallDevices : public QObject
{
Q_OBJECT
Q_OBJECT
public:
static CallDevices &instance()
{
static CallDevices instance;
return instance;
}
bool haveMic() const;
bool haveCamera() const;
std::vector<std::string> names(bool isVideo, const std::string &defaultDevice) const;
std::vector<std::string> resolutions(const std::string &cameraName) const;
std::vector<std::string> frameRates(const std::string &cameraName,
const std::string &resolution) const;
static CallDevices &instance()
{
static CallDevices instance;
return instance;
}
bool haveMic() const;
bool haveCamera() const;
std::vector<std::string> names(bool isVideo, const std::string &defaultDevice) const;
std::vector<std::string> resolutions(const std::string &cameraName) const;
std::vector<std::string> frameRates(const std::string &cameraName,
const std::string &resolution) const;
signals:
void devicesChanged();
void devicesChanged();
private:
CallDevices();
CallDevices();
friend class WebRTCSession;
void init();
GstDevice *audioDevice() const;
GstDevice *videoDevice(std::pair<int, int> &resolution,
std::pair<int, int> &frameRate) const;
friend class WebRTCSession;
void init();
GstDevice *audioDevice() const;
GstDevice *videoDevice(std::pair<int, int> &resolution, std::pair<int, int> &frameRate) const;
public:
CallDevices(CallDevices const &) = delete;
void operator=(CallDevices const &) = delete;
CallDevices(CallDevices const &) = delete;
void operator=(CallDevices const &) = delete;
};

File diff suppressed because it is too large Load Diff

@ -26,93 +26,92 @@ class QUrl;
class CallManager : public QObject
{
Q_OBJECT
Q_PROPERTY(bool haveCallInvite READ haveCallInvite NOTIFY newInviteState)
Q_PROPERTY(bool isOnCall READ isOnCall NOTIFY newCallState)
Q_PROPERTY(webrtc::CallType callType READ callType NOTIFY newInviteState)
Q_PROPERTY(webrtc::State callState READ callState NOTIFY newCallState)
Q_PROPERTY(QString callParty READ callParty NOTIFY newInviteState)
Q_PROPERTY(QString callPartyDisplayName READ callPartyDisplayName NOTIFY newInviteState)
Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY newInviteState)
Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged)
Q_PROPERTY(bool haveLocalPiP READ haveLocalPiP NOTIFY newCallState)
Q_PROPERTY(QStringList mics READ mics NOTIFY devicesChanged)
Q_PROPERTY(QStringList cameras READ cameras NOTIFY devicesChanged)
Q_PROPERTY(bool callsSupported READ callsSupported CONSTANT)
Q_PROPERTY(bool screenShareSupported READ screenShareSupported CONSTANT)
Q_OBJECT
Q_PROPERTY(bool haveCallInvite READ haveCallInvite NOTIFY newInviteState)
Q_PROPERTY(bool isOnCall READ isOnCall NOTIFY newCallState)
Q_PROPERTY(webrtc::CallType callType READ callType NOTIFY newInviteState)
Q_PROPERTY(webrtc::State callState READ callState NOTIFY newCallState)
Q_PROPERTY(QString callParty READ callParty NOTIFY newInviteState)
Q_PROPERTY(QString callPartyDisplayName READ callPartyDisplayName NOTIFY newInviteState)
Q_PROPERTY(QString callPartyAvatarUrl READ callPartyAvatarUrl NOTIFY newInviteState)
Q_PROPERTY(bool isMicMuted READ isMicMuted NOTIFY micMuteChanged)
Q_PROPERTY(bool haveLocalPiP READ haveLocalPiP NOTIFY newCallState)
Q_PROPERTY(QStringList mics READ mics NOTIFY devicesChanged)
Q_PROPERTY(QStringList cameras READ cameras NOTIFY devicesChanged)
Q_PROPERTY(bool callsSupported READ callsSupported CONSTANT)
Q_PROPERTY(bool screenShareSupported READ screenShareSupported CONSTANT)
public:
CallManager(QObject *);
CallManager(QObject *);
bool haveCallInvite() const { return haveCallInvite_; }
bool isOnCall() const { return session_.state() != webrtc::State::DISCONNECTED; }
webrtc::CallType callType() const { return callType_; }
webrtc::State callState() const { return session_.state(); }
QString callParty() const { return callParty_; }
QString callPartyDisplayName() const { return callPartyDisplayName_; }
QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; }
bool isMicMuted() const { return session_.isMicMuted(); }
bool haveLocalPiP() const { return session_.haveLocalPiP(); }
QStringList mics() const { return devices(false); }
QStringList cameras() const { return devices(true); }
void refreshTurnServer();
bool haveCallInvite() const { return haveCallInvite_; }
bool isOnCall() const { return session_.state() != webrtc::State::DISCONNECTED; }
webrtc::CallType callType() const { return callType_; }
webrtc::State callState() const { return session_.state(); }
QString callParty() const { return callParty_; }
QString callPartyDisplayName() const { return callPartyDisplayName_; }
QString callPartyAvatarUrl() const { return callPartyAvatarUrl_; }
bool isMicMuted() const { return session_.isMicMuted(); }
bool haveLocalPiP() const { return session_.haveLocalPiP(); }
QStringList mics() const { return devices(false); }
QStringList cameras() const { return devices(true); }
void refreshTurnServer();
static bool callsSupported();
static bool screenShareSupported();
static bool callsSupported();
static bool screenShareSupported();
public slots:
void sendInvite(const QString &roomid, webrtc::CallType, unsigned int windowIndex = 0);
void syncEvent(const mtx::events::collections::TimelineEvents &event);
void toggleMicMute();
void toggleLocalPiP() { session_.toggleLocalPiP(); }
void acceptInvite();
void hangUp(
mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User);
QStringList windowList();
void previewWindow(unsigned int windowIndex) const;
void sendInvite(const QString &roomid, webrtc::CallType, unsigned int windowIndex = 0);
void syncEvent(const mtx::events::collections::TimelineEvents &event);
void toggleMicMute();
void toggleLocalPiP() { session_.toggleLocalPiP(); }
void acceptInvite();
void hangUp(mtx::events::msg::CallHangUp::Reason = mtx::events::msg::CallHangUp::Reason::User);
QStringList windowList();
void previewWindow(unsigned int windowIndex) const;
signals:
void newMessage(const QString &roomid, const mtx::events::msg::CallInvite &);
void newMessage(const QString &roomid, const mtx::events::msg::CallCandidates &);
void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer &);
void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp &);
void newInviteState();
void newCallState();
void micMuteChanged();
void devicesChanged();
void turnServerRetrieved(const mtx::responses::TurnServer &);
void newMessage(const QString &roomid, const mtx::events::msg::CallInvite &);
void newMessage(const QString &roomid, const mtx::events::msg::CallCandidates &);
void newMessage(const QString &roomid, const mtx::events::msg::CallAnswer &);
void newMessage(const QString &roomid, const mtx::events::msg::CallHangUp &);
void newInviteState();
void newCallState();
void micMuteChanged();
void devicesChanged();
void turnServerRetrieved(const mtx::responses::TurnServer &);
private slots:
void retrieveTurnServer();
void retrieveTurnServer();
private:
WebRTCSession &session_;
QString roomid_;
QString callParty_;
QString callPartyDisplayName_;
QString callPartyAvatarUrl_;
std::string callid_;
const uint32_t timeoutms_ = 120000;
webrtc::CallType callType_ = webrtc::CallType::VOICE;
bool haveCallInvite_ = false;
std::string inviteSDP_;
std::vector<mtx::events::msg::CallCandidates::Candidate> remoteICECandidates_;
std::vector<std::string> turnURIs_;
QTimer turnServerTimer_;
QMediaPlayer player_;
std::vector<std::pair<QString, uint32_t>> windows_;
WebRTCSession &session_;
QString roomid_;
QString callParty_;
QString callPartyDisplayName_;
QString callPartyAvatarUrl_;
std::string callid_;
const uint32_t timeoutms_ = 120000;
webrtc::CallType callType_ = webrtc::CallType::VOICE;
bool haveCallInvite_ = false;
std::string inviteSDP_;
std::vector<mtx::events::msg::CallCandidates::Candidate> remoteICECandidates_;
std::vector<std::string> turnURIs_;
QTimer turnServerTimer_;
QMediaPlayer player_;
std::vector<std::pair<QString, uint32_t>> windows_;
template<typename T>
bool handleEvent(const mtx::events::collections::TimelineEvents &event);
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallInvite> &);
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallCandidates> &);
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallAnswer> &);
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &);
void answerInvite(const mtx::events::msg::CallInvite &, bool isVideo);
void generateCallID();
QStringList devices(bool isVideo) const;
void clear();
void endCall();
void playRingtone(const QUrl &ringtone, bool repeat);
void stopRingtone();
template<typename T>
bool handleEvent(const mtx::events::collections::TimelineEvents &event);
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallInvite> &);
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallCandidates> &);
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallAnswer> &);
void handleEvent(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &);
void answerInvite(const mtx::events::msg::CallInvite &, bool isVideo);
void generateCallID();
QStringList devices(bool isVideo) const;
void clear();
void endCall();
void playRingtone(const QUrl &ringtone, bool repeat);
void stopRingtone();
};

File diff suppressed because it is too large Load Diff

@ -52,186 +52,181 @@ using SecretsToDecrypt = std::map<std::string, mtx::secret_storage::AesHmacSha2E
class ChatPage : public QWidget
{
Q_OBJECT
Q_OBJECT
public:
ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent = nullptr);
ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent = nullptr);
// Initialize all the components of the UI.
void bootstrap(QString userid, QString homeserver, QString token);
// Initialize all the components of the UI.
void bootstrap(QString userid, QString homeserver, QString token);
static ChatPage *instance() { return instance_; }
static ChatPage *instance() { return instance_; }
QSharedPointer<UserSettings> userSettings() { return userSettings_; }
CallManager *callManager() { return callManager_; }
TimelineViewManager *timelineManager() { return view_manager_; }
void deleteConfigs();
QSharedPointer<UserSettings> userSettings() { return userSettings_; }
CallManager *callManager() { return callManager_; }
TimelineViewManager *timelineManager() { return view_manager_; }
void deleteConfigs();
void initiateLogout();
void initiateLogout();
QString status() const;
void setStatus(const QString &status);
QString status() const;
void setStatus(const QString &status);
mtx::presence::PresenceState currentPresence() const;
mtx::presence::PresenceState currentPresence() const;
// TODO(Nico): Get rid of this!
QString currentRoom() const;
// TODO(Nico): Get rid of this!
QString currentRoom() const;
public slots:
bool handleMatrixUri(const QByteArray &uri);
bool handleMatrixUri(const QUrl &uri);
void startChat(QString userid);
void leaveRoom(const QString &room_id);
void createRoom(const mtx::requests::CreateRoom &req);
void joinRoom(const QString &room);
void joinRoomVia(const std::string &room_id,
const std::vector<std::string> &via,
bool promptForConfirmation = true);
void inviteUser(QString userid, QString reason);
void kickUser(QString userid, QString reason);
void banUser(QString userid, QString reason);
void unbanUser(QString userid, QString reason);
void receivedSessionKey(const std::string &room_id, const std::string &session_id);
void decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
const SecretsToDecrypt &secrets);
bool handleMatrixUri(const QByteArray &uri);
bool handleMatrixUri(const QUrl &uri);
void startChat(QString userid);
void leaveRoom(const QString &room_id);
void createRoom(const mtx::requests::CreateRoom &req);
void joinRoom(const QString &room);
void joinRoomVia(const std::string &room_id,
const std::vector<std::string> &via,
bool promptForConfirmation = true);
void inviteUser(QString userid, QString reason);
void kickUser(QString userid, QString reason);
void banUser(QString userid, QString reason);
void unbanUser(QString userid, QString reason);
void receivedSessionKey(const std::string &room_id, const std::string &session_id);
void decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
const SecretsToDecrypt &secrets);
signals:
void connectionLost();
void connectionRestored();
void notificationsRetrieved(const mtx::responses::Notifications &);
void highlightedNotifsRetrieved(const mtx::responses::Notifications &,
const QPoint widgetPos);
void contentLoaded();
void closing();
void changeWindowTitle(const int);
void unreadMessages(int count);
void showNotification(const QString &msg);
void showLoginPage(const QString &msg);
void showUserSettingsPage();
void showOverlayProgressBar();
void ownProfileOk();
void setUserDisplayName(const QString &name);
void setUserAvatar(const QString &avatar);
void loggedOut();
void trySyncCb();
void tryDelayedSyncCb();
void tryInitialSyncCb();
void newSyncResponse(const mtx::responses::Sync &res, const std::string &prev_batch_token);
void leftRoom(const QString &room_id);
void newRoom(const QString &room_id);
void changeToRoom(const QString &room_id);
void initializeViews(const mtx::responses::Rooms &rooms);
void initializeEmptyViews();
void initializeMentions(const QMap<QString, mtx::responses::Notifications> &notifs);
void syncUI(const mtx::responses::Rooms &rooms);
void dropToLoginPageCb(const QString &msg);
void notifyMessage(const QString &roomid,
const QString &eventid,
const QString &roomname,
const QString &sender,
const QString &message,
const QImage &icon);
void retrievedPresence(const QString &statusMsg, mtx::presence::PresenceState state);
void themeChanged();
void decryptSidebarChanged();
void chatFocusChanged(const bool focused);
//! Signals for device verificaiton
void receivedDeviceVerificationAccept(
const mtx::events::msg::KeyVerificationAccept &message);
void receivedDeviceVerificationRequest(
const mtx::events::msg::KeyVerificationRequest &message,
std::string sender);
void receivedRoomDeviceVerificationRequest(
const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &message,
TimelineModel *model);
void receivedDeviceVerificationCancel(
const mtx::events::msg::KeyVerificationCancel &message);
void receivedDeviceVerificationKey(const mtx::events::msg::KeyVerificationKey &message);
void receivedDeviceVerificationMac(const mtx::events::msg::KeyVerificationMac &message);
void receivedDeviceVerificationStart(const mtx::events::msg::KeyVerificationStart &message,
std::string sender);
void receivedDeviceVerificationReady(const mtx::events::msg::KeyVerificationReady &message);
void receivedDeviceVerificationDone(const mtx::events::msg::KeyVerificationDone &message);
void downloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
const SecretsToDecrypt &secrets);
void connectionLost();
void connectionRestored();
void notificationsRetrieved(const mtx::responses::Notifications &);
void highlightedNotifsRetrieved(const mtx::responses::Notifications &, const QPoint widgetPos);
void contentLoaded();
void closing();
void changeWindowTitle(const int);
void unreadMessages(int count);
void showNotification(const QString &msg);
void showLoginPage(const QString &msg);
void showUserSettingsPage();
void showOverlayProgressBar();
void ownProfileOk();
void setUserDisplayName(const QString &name);
void setUserAvatar(const QString &avatar);
void loggedOut();
void trySyncCb();
void tryDelayedSyncCb();
void tryInitialSyncCb();
void newSyncResponse(const mtx::responses::Sync &res, const std::string &prev_batch_token);
void leftRoom(const QString &room_id);
void newRoom(const QString &room_id);
void changeToRoom(const QString &room_id);
void initializeViews(const mtx::responses::Rooms &rooms);
void initializeEmptyViews();
void initializeMentions(const QMap<QString, mtx::responses::Notifications> &notifs);
void syncUI(const mtx::responses::Rooms &rooms);
void dropToLoginPageCb(const QString &msg);
void notifyMessage(const QString &roomid,
const QString &eventid,
const QString &roomname,
const QString &sender,
const QString &message,
const QImage &icon);
void retrievedPresence(const QString &statusMsg, mtx::presence::PresenceState state);
void themeChanged();
void decryptSidebarChanged();
void chatFocusChanged(const bool focused);
//! Signals for device verificaiton
void receivedDeviceVerificationAccept(const mtx::events::msg::KeyVerificationAccept &message);
void receivedDeviceVerificationRequest(const mtx::events::msg::KeyVerificationRequest &message,
std::string sender);
void receivedRoomDeviceVerificationRequest(
const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &message,
TimelineModel *model);
void receivedDeviceVerificationCancel(const mtx::events::msg::KeyVerificationCancel &message);
void receivedDeviceVerificationKey(const mtx::events::msg::KeyVerificationKey &message);
void receivedDeviceVerificationMac(const mtx::events::msg::KeyVerificationMac &message);
void receivedDeviceVerificationStart(const mtx::events::msg::KeyVerificationStart &message,
std::string sender);
void receivedDeviceVerificationReady(const mtx::events::msg::KeyVerificationReady &message);
void receivedDeviceVerificationDone(const mtx::events::msg::KeyVerificationDone &message);
void downloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
const SecretsToDecrypt &secrets);
private slots:
void logout();
void removeRoom(const QString &room_id);
void changeRoom(const QString &room_id);
void dropToLoginPage(const QString &msg);
void logout();
void removeRoom(const QString &room_id);
void changeRoom(const QString &room_id);
void dropToLoginPage(const QString &msg);
void handleSyncResponse(const mtx::responses::Sync &res,
const std::string &prev_batch_token);
void handleSyncResponse(const mtx::responses::Sync &res, const std::string &prev_batch_token);
private:
static ChatPage *instance_;
static ChatPage *instance_;
void startInitialSync();
void tryInitialSync();
void trySync();
void verifyOneTimeKeyCountAfterStartup();
void ensureOneTimeKeyCount(const std::map<std::string, uint16_t> &counts);
void getProfileInfo();
void getBackupVersion();
void startInitialSync();
void tryInitialSync();
void trySync();
void verifyOneTimeKeyCountAfterStartup();
void ensureOneTimeKeyCount(const std::map<std::string, uint16_t> &counts);
void getProfileInfo();
void getBackupVersion();
//! Check if the given room is currently open.
bool isRoomActive(const QString &room_id);
//! Check if the given room is currently open.
bool isRoomActive(const QString &room_id);
using UserID = QString;
using Membership = mtx::events::StateEvent<mtx::events::state::Member>;
using Memberships = std::map<std::string, Membership>;
using UserID = QString;
using Membership = mtx::events::StateEvent<mtx::events::state::Member>;
using Memberships = std::map<std::string, Membership>;
void loadStateFromCache();
void resetUI();
void loadStateFromCache();
void resetUI();
template<class Collection>
Memberships getMemberships(const std::vector<Collection> &events) const;
template<class Collection>
Memberships getMemberships(const std::vector<Collection> &events) const;
//! Send desktop notification for the received messages.
void sendNotifications(const mtx::responses::Notifications &);
//! Send desktop notification for the received messages.
void sendNotifications(const mtx::responses::Notifications &);
template<typename T>
void connectCallMessage();
template<typename T>
void connectCallMessage();
QHBoxLayout *topLayout_;
QHBoxLayout *topLayout_;
TimelineViewManager *view_manager_;
TimelineViewManager *view_manager_;
QTimer connectivityTimer_;
std::atomic_bool isConnected_;
QTimer connectivityTimer_;
std::atomic_bool isConnected_;
// Global user settings.
QSharedPointer<UserSettings> userSettings_;
// Global user settings.
QSharedPointer<UserSettings> userSettings_;
NotificationsManager notificationsManager;
CallManager *callManager_;
NotificationsManager notificationsManager;
CallManager *callManager_;
};
template<class Collection>
std::map<std::string, mtx::events::StateEvent<mtx::events::state::Member>>
ChatPage::getMemberships(const std::vector<Collection> &collection) const
{
std::map<std::string, mtx::events::StateEvent<mtx::events::state::Member>> memberships;
std::map<std::string, mtx::events::StateEvent<mtx::events::state::Member>> memberships;
using Member = mtx::events::StateEvent<mtx::events::state::Member>;
using Member = mtx::events::StateEvent<mtx::events::state::Member>;
for (const auto &event : collection) {
if (auto member = std::get_if<Member>(event)) {
memberships.emplace(member->state_key, *member);
}
for (const auto &event : collection) {
if (auto member = std::get_if<Member>(event)) {
memberships.emplace(member->state_key, *member);
}
}
return memberships;
return memberships;
}

@ -10,18 +10,17 @@
Clipboard::Clipboard(QObject *parent)
: QObject(parent)
{
connect(
QGuiApplication::clipboard(), &QClipboard::dataChanged, this, &Clipboard::textChanged);
connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, this, &Clipboard::textChanged);
}
void
Clipboard::setText(QString text)
{
QGuiApplication::clipboard()->setText(text);
QGuiApplication::clipboard()->setText(text);
}
QString
Clipboard::text() const
{
return QGuiApplication::clipboard()->text();
return QGuiApplication::clipboard()->text();
}

@ -9,14 +9,14 @@
class Clipboard : public QObject
{
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
Q_OBJECT
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
public:
Clipboard(QObject *parent = nullptr);
Clipboard(QObject *parent = nullptr);
QString text() const;
void setText(QString text_);
QString text() const;
void setText(QString text_);
signals:
void textChanged();
void textChanged();
};

@ -9,23 +9,23 @@
QPixmap
ColorImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &)
{
auto args = id.split('?');
auto args = id.split('?');
QPixmap source(args[0]);
QPixmap source(args[0]);
if (size)
*size = QSize(source.width(), source.height());
if (size)
*size = QSize(source.width(), source.height());
if (args.size() < 2)
return source;
if (args.size() < 2)
return source;
QColor color(args[1]);
QColor color(args[1]);
QPixmap colorized = source;
QPainter painter(&colorized);
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
painter.fillRect(colorized.rect(), color);
painter.end();
QPixmap colorized = source;
QPainter painter(&colorized);
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
painter.fillRect(colorized.rect(), color);
painter.end();
return colorized;
return colorized;
}

@ -7,9 +7,9 @@
class ColorImageProvider : public QQuickImageProvider
{
public:
ColorImageProvider()
: QQuickImageProvider(QQuickImageProvider::Pixmap)
{}
ColorImageProvider()
: QQuickImageProvider(QQuickImageProvider::Pixmap)
{}
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
};

@ -13,65 +13,65 @@ CombinedImagePackModel::CombinedImagePackModel(const std::string &roomId,
: QAbstractListModel(parent)
, room_id(roomId)
{
auto packs = cache::client()->getImagePacks(room_id, stickers);
auto packs = cache::client()->getImagePacks(room_id, stickers);
for (const auto &pack : packs) {
QString packname =
pack.pack.pack ? QString::fromStdString(pack.pack.pack->display_name) : "";
for (const auto &pack : packs) {
QString packname =
pack.pack.pack ? QString::fromStdString(pack.pack.pack->display_name) : "";
for (const auto &img : pack.pack.images) {
ImageDesc i{};
i.shortcode = QString::fromStdString(img.first);
i.packname = packname;
i.image = img.second;
images.push_back(std::move(i));
}
for (const auto &img : pack.pack.images) {
ImageDesc i{};
i.shortcode = QString::fromStdString(img.first);
i.packname = packname;
i.image = img.second;
images.push_back(std::move(i));
}
}
}
int
CombinedImagePackModel::rowCount(const QModelIndex &) const
{
return (int)images.size();
return (int)images.size();
}
QHash<int, QByteArray>
CombinedImagePackModel::roleNames() const
{
return {
{CompletionModel::CompletionRole, "completionRole"},
{CompletionModel::SearchRole, "searchRole"},
{CompletionModel::SearchRole2, "searchRole2"},
{Roles::Url, "url"},
{Roles::ShortCode, "shortcode"},
{Roles::Body, "body"},
{Roles::PackName, "packname"},
{Roles::OriginalRow, "originalRow"},
};
return {
{CompletionModel::CompletionRole, "completionRole"},
{CompletionModel::SearchRole, "searchRole"},
{CompletionModel::SearchRole2, "searchRole2"},
{Roles::Url, "url"},
{Roles::ShortCode, "shortcode"},
{Roles::Body, "body"},
{Roles::PackName, "packname"},
{Roles::OriginalRow, "originalRow"},
};
}
QVariant
CombinedImagePackModel::data(const QModelIndex &index, int role) const
{
if (hasIndex(index.row(), index.column(), index.parent())) {
switch (role) {
case CompletionModel::CompletionRole:
return QString::fromStdString(images[index.row()].image.url);
case Roles::Url:
return QString::fromStdString(images[index.row()].image.url);
case CompletionModel::SearchRole:
case Roles::ShortCode:
return images[index.row()].shortcode;
case CompletionModel::SearchRole2:
case Roles::Body:
return QString::fromStdString(images[index.row()].image.body);
case Roles::PackName:
return images[index.row()].packname;
case Roles::OriginalRow:
return index.row();
default:
return {};
}
if (hasIndex(index.row(), index.column(), index.parent())) {
switch (role) {
case CompletionModel::CompletionRole:
return QString::fromStdString(images[index.row()].image.url);
case Roles::Url:
return QString::fromStdString(images[index.row()].image.url);
case CompletionModel::SearchRole:
case Roles::ShortCode:
return images[index.row()].shortcode;
case CompletionModel::SearchRole2:
case Roles::Body:
return QString::fromStdString(images[index.row()].image.body);
case Roles::PackName:
return images[index.row()].packname;
case Roles::OriginalRow:
return index.row();
default:
return {};
}
return {};
}
return {};
}

@ -10,39 +10,39 @@
class CombinedImagePackModel : public QAbstractListModel
{
Q_OBJECT
Q_OBJECT
public:
enum Roles
{
Url = Qt::UserRole,
ShortCode,
Body,
PackName,
OriginalRow,
};
CombinedImagePackModel(const std::string &roomId, bool stickers, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
mtx::events::msc2545::PackImage imageAt(int row)
{
if (row < 0 || static_cast<size_t>(row) >= images.size())
return {};
return images.at(static_cast<size_t>(row)).image;
}
enum Roles
{
Url = Qt::UserRole,
ShortCode,
Body,
PackName,
OriginalRow,
};
CombinedImagePackModel(const std::string &roomId, bool stickers, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
mtx::events::msc2545::PackImage imageAt(int row)
{
if (row < 0 || static_cast<size_t>(row) >= images.size())
return {};
return images.at(static_cast<size_t>(row)).image;
}
private:
std::string room_id;
std::string room_id;
struct ImageDesc
{
QString shortcode;
QString packname;
struct ImageDesc
{
QString shortcode;
QString packname;
mtx::events::msc2545::PackImage image;
};
mtx::events::msc2545::PackImage image;
};
std::vector<ImageDesc> images;
std::vector<ImageDesc> images;
};

@ -12,8 +12,8 @@ namespace CompletionModel {
// Start at Qt::UserRole * 2 to prevent clashes
enum Roles
{
CompletionRole = Qt::UserRole * 2, // The string to replace the active completion
SearchRole, // String completer uses for search
SearchRole2, // Secondary string completer uses for search
CompletionRole = Qt::UserRole * 2, // The string to replace the active completion
SearchRole, // String completer uses for search
SearchRole2, // Secondary string completer uses for search
};
}

@ -18,154 +18,154 @@ CompletionProxyModel::CompletionProxyModel(QAbstractItemModel *model,
, maxMistakes_(max_mistakes)
, max_completions_(max_completions)
{
setSourceModel(model);
QChar splitPoints(' ');
// insert all the full texts
for (int i = 0; i < sourceModel()->rowCount(); i++) {
if (static_cast<size_t>(i) < max_completions_)
mapping.push_back(i);
auto string1 = sourceModel()
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole)
.toString()
.toLower();
if (!string1.isEmpty())
trie_.insert(string1.toUcs4(), i);
auto string2 = sourceModel()
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2)
.toString()
.toLower();
if (!string2.isEmpty())
trie_.insert(string2.toUcs4(), i);
setSourceModel(model);
QChar splitPoints(' ');
// insert all the full texts
for (int i = 0; i < sourceModel()->rowCount(); i++) {
if (static_cast<size_t>(i) < max_completions_)
mapping.push_back(i);
auto string1 = sourceModel()
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole)
.toString()
.toLower();
if (!string1.isEmpty())
trie_.insert(string1.toUcs4(), i);
auto string2 = sourceModel()
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2)
.toString()
.toLower();
if (!string2.isEmpty())
trie_.insert(string2.toUcs4(), i);
}
// insert the partial matches
for (int i = 0; i < sourceModel()->rowCount(); i++) {
auto string1 = sourceModel()
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole)
.toString()
.toLower();
for (const auto &e : string1.splitRef(splitPoints)) {
if (!e.isEmpty()) // NOTE(Nico): Use Qt::SkipEmptyParts in Qt 5.14
trie_.insert(e.toUcs4(), i);
}
// insert the partial matches
for (int i = 0; i < sourceModel()->rowCount(); i++) {
auto string1 = sourceModel()
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole)
.toString()
.toLower();
for (const auto &e : string1.splitRef(splitPoints)) {
if (!e.isEmpty()) // NOTE(Nico): Use Qt::SkipEmptyParts in Qt 5.14
trie_.insert(e.toUcs4(), i);
}
auto string2 = sourceModel()
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2)
.toString()
.toLower();
if (!string2.isEmpty()) {
for (const auto &e : string2.splitRef(splitPoints)) {
if (!e.isEmpty()) // NOTE(Nico): Use Qt::SkipEmptyParts in Qt 5.14
trie_.insert(e.toUcs4(), i);
}
}
}
auto string2 = sourceModel()
->data(sourceModel()->index(i, 0), CompletionModel::SearchRole2)
.toString()
.toLower();
connect(
this,
&CompletionProxyModel::newSearchString,
this,
[this](QString s) {
s.remove(":");
s.remove("@");
searchString_ = s.toLower();
invalidate();
},
Qt::QueuedConnection);
if (!string2.isEmpty()) {
for (const auto &e : string2.splitRef(splitPoints)) {
if (!e.isEmpty()) // NOTE(Nico): Use Qt::SkipEmptyParts in Qt 5.14
trie_.insert(e.toUcs4(), i);
}
}
}
connect(
this,
&CompletionProxyModel::newSearchString,
this,
[this](QString s) {
s.remove(":");
s.remove("@");
searchString_ = s.toLower();
invalidate();
},
Qt::QueuedConnection);
}
void
CompletionProxyModel::invalidate()
{
auto key = searchString_.toUcs4();
beginResetModel();
if (!key.empty()) // return default model data, if no search string
mapping = trie_.search(key, max_completions_, maxMistakes_);
endResetModel();
auto key = searchString_.toUcs4();
beginResetModel();
if (!key.empty()) // return default model data, if no search string
mapping = trie_.search(key, max_completions_, maxMistakes_);
endResetModel();
}
QHash<int, QByteArray>
CompletionProxyModel::roleNames() const
{
return this->sourceModel()->roleNames();
return this->sourceModel()->roleNames();
}
int
CompletionProxyModel::rowCount(const QModelIndex &) const
{
if (searchString_.isEmpty())
return std::min(static_cast<int>(std::min<size_t>(max_completions_,
std::numeric_limits<int>::max())),
sourceModel()->rowCount());
else
return (int)mapping.size();
if (searchString_.isEmpty())
return std::min(
static_cast<int>(std::min<size_t>(max_completions_, std::numeric_limits<int>::max())),
sourceModel()->rowCount());
else
return (int)mapping.size();
}
QModelIndex
CompletionProxyModel::mapFromSource(const QModelIndex &sourceIndex) const
{
// return default model data, if no search string
if (searchString_.isEmpty()) {
return index(sourceIndex.row(), 0);
}
for (int i = 0; i < (int)mapping.size(); i++) {
if (mapping[i] == sourceIndex.row()) {
return index(i, 0);
}
// return default model data, if no search string
if (searchString_.isEmpty()) {
return index(sourceIndex.row(), 0);
}
for (int i = 0; i < (int)mapping.size(); i++) {
if (mapping[i] == sourceIndex.row()) {
return index(i, 0);
}
return QModelIndex();
}
return QModelIndex();
}
QModelIndex
CompletionProxyModel::mapToSource(const QModelIndex &proxyIndex) const
{
auto row = proxyIndex.row();
auto row = proxyIndex.row();
// return default model data, if no search string
if (searchString_.isEmpty()) {
return index(row, 0);
}
// return default model data, if no search string
if (searchString_.isEmpty()) {
return index(row, 0);
}
if (row < 0 || row >= (int)mapping.size())
return QModelIndex();
if (row < 0 || row >= (int)mapping.size())
return QModelIndex();
return sourceModel()->index(mapping[row], 0);
return sourceModel()->index(mapping[row], 0);
}
QModelIndex
CompletionProxyModel::index(int row, int column, const QModelIndex &) const
{
return createIndex(row, column);
return createIndex(row, column);
}
QModelIndex
CompletionProxyModel::parent(const QModelIndex &) const
{
return QModelIndex{};
return QModelIndex{};
}
int
CompletionProxyModel::columnCount(const QModelIndex &) const
{
return sourceModel()->columnCount();
return sourceModel()->columnCount();
}
QVariant
CompletionProxyModel::completionAt(int i) const
{
if (i >= 0 && i < rowCount())
return data(index(i, 0), CompletionModel::CompletionRole);
else
return {};
if (i >= 0 && i < rowCount())
return data(index(i, 0), CompletionModel::CompletionRole);
else
return {};
}
void
CompletionProxyModel::setSearchString(QString s)
{
emit newSearchString(s);
emit newSearchString(s);
}

@ -11,179 +11,176 @@
template<typename Key, typename Value>
struct trie
{
std::vector<Value> values;
std::map<Key, trie> next;
void insert(const QVector<Key> &keys, const Value &v)
{
auto t = this;
for (const auto k : keys) {
t = &t->next[k];
}
t->values.push_back(v);
std::vector<Value> values;
std::map<Key, trie> next;
void insert(const QVector<Key> &keys, const Value &v)
{
auto t = this;
for (const auto k : keys) {
t = &t->next[k];
}
std::vector<Value> valuesAndSubvalues(size_t limit = -1) const
{
std::vector<Value> ret;
if (limit < 200)
ret.reserve(limit);
for (const auto &v : values) {
if (ret.size() >= limit)
return ret;
else
ret.push_back(v);
}
t->values.push_back(v);
}
for (const auto &[k, t] : next) {
(void)k;
if (ret.size() >= limit)
return ret;
else {
auto temp = t.valuesAndSubvalues(limit - ret.size());
for (auto &&v : temp) {
if (ret.size() >= limit)
return ret;
if (std::find(ret.begin(), ret.end(), v) == ret.end()) {
ret.push_back(std::move(v));
}
}
}
}
std::vector<Value> valuesAndSubvalues(size_t limit = -1) const
{
std::vector<Value> ret;
if (limit < 200)
ret.reserve(limit);
for (const auto &v : values) {
if (ret.size() >= limit)
return ret;
else
ret.push_back(v);
}
std::vector<Value> search(const QVector<Key> &keys, //< TODO(Nico): replace this with a span
size_t result_count_limit,
size_t max_edit_distance_ = 2) const
{
std::vector<Value> ret;
if (!result_count_limit)
for (const auto &[k, t] : next) {
(void)k;
if (ret.size() >= limit)
return ret;
else {
auto temp = t.valuesAndSubvalues(limit - ret.size());
for (auto &&v : temp) {
if (ret.size() >= limit)
return ret;
if (keys.isEmpty())
return valuesAndSubvalues(result_count_limit);
if (std::find(ret.begin(), ret.end(), v) == ret.end()) {
ret.push_back(std::move(v));
}
}
}
}
auto append = [&ret, result_count_limit](std::vector<Value> &&in) {
for (auto &&v : in) {
if (ret.size() >= result_count_limit)
return;
return ret;
}
if (std::find(ret.begin(), ret.end(), v) == ret.end()) {
ret.push_back(std::move(v));
}
}
};
auto limit = [&ret, result_count_limit] {
return std::min(result_count_limit, (result_count_limit - ret.size()) * 2);
};
// Try first exact matches, then with maximum errors
for (size_t max_edit_distance = 0;
max_edit_distance <= max_edit_distance_ && ret.size() < result_count_limit;
max_edit_distance += 1) {
if (max_edit_distance && ret.size() < result_count_limit) {
max_edit_distance -= 1;
// swap chars case
if (keys.size() >= 2) {
auto t = this;
for (int i = 1; i >= 0; i--) {
if (auto e = t->next.find(keys[i]);
e != t->next.end()) {
t = &e->second;
} else {
t = nullptr;
break;
}
}
if (t) {
append(t->search(
keys.mid(2), limit(), max_edit_distance));
}
}
// insert case
for (const auto &[k, t] : this->next) {
if (k == keys[0])
continue;
if (ret.size() >= limit())
break;
// insert
append(t.search(keys, limit(), max_edit_distance));
}
// delete character case
append(this->search(keys.mid(1), limit(), max_edit_distance));
// substitute case
for (const auto &[k, t] : this->next) {
if (k == keys[0])
continue;
if (ret.size() >= limit())
break;
// substitute
append(t.search(keys.mid(1), limit(), max_edit_distance));
}
max_edit_distance += 1;
}
std::vector<Value> search(const QVector<Key> &keys, //< TODO(Nico): replace this with a span
size_t result_count_limit,
size_t max_edit_distance_ = 2) const
{
std::vector<Value> ret;
if (!result_count_limit)
return ret;
if (keys.isEmpty())
return valuesAndSubvalues(result_count_limit);
auto append = [&ret, result_count_limit](std::vector<Value> &&in) {
for (auto &&v : in) {
if (ret.size() >= result_count_limit)
return;
if (auto e = this->next.find(keys[0]); e != this->next.end()) {
append(e->second.search(keys.mid(1), limit(), max_edit_distance));
if (std::find(ret.begin(), ret.end(), v) == ret.end()) {
ret.push_back(std::move(v));
}
}
};
auto limit = [&ret, result_count_limit] {
return std::min(result_count_limit, (result_count_limit - ret.size()) * 2);
};
// Try first exact matches, then with maximum errors
for (size_t max_edit_distance = 0;
max_edit_distance <= max_edit_distance_ && ret.size() < result_count_limit;
max_edit_distance += 1) {
if (max_edit_distance && ret.size() < result_count_limit) {
max_edit_distance -= 1;
// swap chars case
if (keys.size() >= 2) {
auto t = this;
for (int i = 1; i >= 0; i--) {
if (auto e = t->next.find(keys[i]); e != t->next.end()) {
t = &e->second;
} else {
t = nullptr;
break;
}
}
if (t) {
append(t->search(keys.mid(2), limit(), max_edit_distance));
}
}
return ret;
// insert case
for (const auto &[k, t] : this->next) {
if (k == keys[0])
continue;
if (ret.size() >= limit())
break;
// insert
append(t.search(keys, limit(), max_edit_distance));
}
// delete character case
append(this->search(keys.mid(1), limit(), max_edit_distance));
// substitute case
for (const auto &[k, t] : this->next) {
if (k == keys[0])
continue;
if (ret.size() >= limit())
break;
// substitute
append(t.search(keys.mid(1), limit(), max_edit_distance));
}
max_edit_distance += 1;
}
if (auto e = this->next.find(keys[0]); e != this->next.end()) {
append(e->second.search(keys.mid(1), limit(), max_edit_distance));
}
}
return ret;
}
};
class CompletionProxyModel : public QAbstractProxyModel
{
Q_OBJECT
Q_PROPERTY(
QString searchString READ searchString WRITE setSearchString NOTIFY newSearchString)
Q_OBJECT
Q_PROPERTY(QString searchString READ searchString WRITE setSearchString NOTIFY newSearchString)
public:
CompletionProxyModel(QAbstractItemModel *model,
int max_mistakes = 2,
size_t max_completions = 7,
QObject *parent = nullptr);
CompletionProxyModel(QAbstractItemModel *model,
int max_mistakes = 2,
size_t max_completions = 7,
QObject *parent = nullptr);
void invalidate();
void invalidate();
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &) const override;
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &) const override;
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override;
QModelIndex mapToSource(const QModelIndex &proxyIndex) const override;
QModelIndex index(int row,
int column,
const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &) const override;
QModelIndex index(int row,
int column,
const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &) const override;
public slots:
QVariant completionAt(int i) const;
QVariant completionAt(int i) const;
void setSearchString(QString s);
QString searchString() const { return searchString_; }
void setSearchString(QString s);
QString searchString() const { return searchString_; }
signals:
void newSearchString(QString);
void newSearchString(QString);
private:
QString searchString_;
trie<uint, int> trie_;
std::vector<int> mapping;
int maxMistakes_;
size_t max_completions_;
QString searchString_;
trie<uint, int> trie_;
std::vector<int> mapping;
int maxMistakes_;
size_t max_completions_;
};

File diff suppressed because it is too large Load Diff

@ -60,192 +60,189 @@ using sas_ptr = std::unique_ptr<mtx::crypto::SAS>;
// clang-format on
class DeviceVerificationFlow : public QObject
{
Q_OBJECT
Q_PROPERTY(QString state READ state NOTIFY stateChanged)
Q_PROPERTY(Error error READ error NOTIFY errorChanged)
Q_PROPERTY(QString userId READ getUserId CONSTANT)
Q_PROPERTY(QString deviceId READ getDeviceId CONSTANT)
Q_PROPERTY(bool sender READ getSender CONSTANT)
Q_PROPERTY(std::vector<int> sasList READ getSasList CONSTANT)
Q_PROPERTY(bool isDeviceVerification READ isDeviceVerification CONSTANT)
Q_PROPERTY(bool isSelfVerification READ isSelfVerification CONSTANT)
Q_OBJECT
Q_PROPERTY(QString state READ state NOTIFY stateChanged)
Q_PROPERTY(Error error READ error NOTIFY errorChanged)
Q_PROPERTY(QString userId READ getUserId CONSTANT)
Q_PROPERTY(QString deviceId READ getDeviceId CONSTANT)
Q_PROPERTY(bool sender READ getSender CONSTANT)
Q_PROPERTY(std::vector<int> sasList READ getSasList CONSTANT)
Q_PROPERTY(bool isDeviceVerification READ isDeviceVerification CONSTANT)
Q_PROPERTY(bool isSelfVerification READ isSelfVerification CONSTANT)
public:
enum State
{
PromptStartVerification,
WaitingForOtherToAccept,
WaitingForKeys,
CompareEmoji,
CompareNumber,
WaitingForMac,
Success,
Failed,
};
Q_ENUM(State)
enum Type
{
ToDevice,
RoomMsg
};
enum Error
{
UnknownMethod,
MismatchedCommitment,
MismatchedSAS,
KeyMismatch,
Timeout,
User,
OutOfOrder,
};
Q_ENUM(Error)
static QSharedPointer<DeviceVerificationFlow> NewInRoomVerification(
QObject *parent_,
TimelineModel *timelineModel_,
const mtx::events::msg::KeyVerificationRequest &msg,
QString other_user_,
QString event_id_);
static QSharedPointer<DeviceVerificationFlow> NewToDeviceVerification(
QObject *parent_,
const mtx::events::msg::KeyVerificationRequest &msg,
QString other_user_,
QString txn_id_);
static QSharedPointer<DeviceVerificationFlow> NewToDeviceVerification(
QObject *parent_,
const mtx::events::msg::KeyVerificationStart &msg,
QString other_user_,
QString txn_id_);
static QSharedPointer<DeviceVerificationFlow>
InitiateUserVerification(QObject *parent_, TimelineModel *timelineModel_, QString userid);
static QSharedPointer<DeviceVerificationFlow> InitiateDeviceVerification(QObject *parent,
QString userid,
QString device);
// getters
QString state();
Error error() { return error_; }
QString getUserId();
QString getDeviceId();
bool getSender();
std::vector<int> getSasList();
QString transactionId() { return QString::fromStdString(this->transaction_id); }
// setters
void setDeviceId(QString deviceID);
void setEventId(std::string event_id);
bool isDeviceVerification() const
{
return this->type == DeviceVerificationFlow::Type::ToDevice;
}
bool isSelfVerification() const;
void callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id);
enum State
{
PromptStartVerification,
WaitingForOtherToAccept,
WaitingForKeys,
CompareEmoji,
CompareNumber,
WaitingForMac,
Success,
Failed,
};
Q_ENUM(State)
enum Type
{
ToDevice,
RoomMsg
};
enum Error
{
UnknownMethod,
MismatchedCommitment,
MismatchedSAS,
KeyMismatch,
Timeout,
User,
OutOfOrder,
};
Q_ENUM(Error)
static QSharedPointer<DeviceVerificationFlow> NewInRoomVerification(
QObject *parent_,
TimelineModel *timelineModel_,
const mtx::events::msg::KeyVerificationRequest &msg,
QString other_user_,
QString event_id_);
static QSharedPointer<DeviceVerificationFlow> NewToDeviceVerification(
QObject *parent_,
const mtx::events::msg::KeyVerificationRequest &msg,
QString other_user_,
QString txn_id_);
static QSharedPointer<DeviceVerificationFlow> NewToDeviceVerification(
QObject *parent_,
const mtx::events::msg::KeyVerificationStart &msg,
QString other_user_,
QString txn_id_);
static QSharedPointer<DeviceVerificationFlow>
InitiateUserVerification(QObject *parent_, TimelineModel *timelineModel_, QString userid);
static QSharedPointer<DeviceVerificationFlow> InitiateDeviceVerification(QObject *parent,
QString userid,
QString device);
// getters
QString state();
Error error() { return error_; }
QString getUserId();
QString getDeviceId();
bool getSender();
std::vector<int> getSasList();
QString transactionId() { return QString::fromStdString(this->transaction_id); }
// setters
void setDeviceId(QString deviceID);
void setEventId(std::string event_id);
bool isDeviceVerification() const
{
return this->type == DeviceVerificationFlow::Type::ToDevice;
}
bool isSelfVerification() const;
void callback_fn(const UserKeyCache &res, mtx::http::RequestErr err, std::string user_id);
public slots:
//! unverifies a device
void unverify();
//! Continues the flow
void next();
//! Cancel the flow
void cancel() { cancelVerification(User); }
//! unverifies a device
void unverify();
//! Continues the flow
void next();
//! Cancel the flow
void cancel() { cancelVerification(User); }
signals:
void refreshProfile();
void stateChanged();
void errorChanged();
void refreshProfile();
void stateChanged();
void errorChanged();
private:
DeviceVerificationFlow(QObject *,
DeviceVerificationFlow::Type flow_type,
TimelineModel *model,
QString userID,
QString deviceId_);
void setState(State state)
{
if (state != state_) {
state_ = state;
emit stateChanged();
}
DeviceVerificationFlow(QObject *,
DeviceVerificationFlow::Type flow_type,
TimelineModel *model,
QString userID,
QString deviceId_);
void setState(State state)
{
if (state != state_) {
state_ = state;
emit stateChanged();
}
void handleStartMessage(const mtx::events::msg::KeyVerificationStart &msg, std::string);
//! sends a verification request
void sendVerificationRequest();
//! accepts a verification request
void sendVerificationReady();
//! completes the verification flow();
void sendVerificationDone();
//! accepts a verification
void acceptVerificationRequest();
//! starts the verification flow
void startVerificationRequest();
//! cancels a verification flow
void cancelVerification(DeviceVerificationFlow::Error error_code);
//! sends the verification key
void sendVerificationKey();
//! sends the mac of the keys
void sendVerificationMac();
//! Completes the verification flow
void acceptDevice();
std::string transaction_id;
bool sender;
Type type;
mtx::identifiers::User toClient;
QString deviceId;
// public part of our master key, when trusted or empty
std::string our_trusted_master_key;
mtx::events::msg::SASMethods method = mtx::events::msg::SASMethods::Emoji;
QTimer *timeout = nullptr;
sas_ptr sas;
std::string mac_method;
std::string commitment;
nlohmann::json canonical_json;
std::vector<int> sasList;
UserKeyCache their_keys;
TimelineModel *model_;
mtx::common::Relation relation;
State state_ = PromptStartVerification;
Error error_ = UnknownMethod;
bool isMacVerified = false;
template<typename T>
void send(T msg)
{
if (this->type == DeviceVerificationFlow::Type::ToDevice) {
mtx::requests::ToDeviceMessages<T> body;
msg.transaction_id = this->transaction_id;
body[this->toClient][deviceId.toStdString()] = msg;
http::client()->send_to_device<T>(
this->transaction_id, body, [](mtx::http::RequestErr err) {
if (err)
nhlog::net()->warn(
"failed to send verification to_device message: {} {}",
err->matrix_error.error,
static_cast<int>(err->status_code));
});
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
if constexpr (!std::is_same_v<T,
mtx::events::msg::KeyVerificationRequest>) {
msg.relations.relations.push_back(this->relation);
// Set synthesized to surpress the nheko relation extensions
msg.relations.synthesized = true;
}
(model_)->sendMessageEvent(msg, mtx::events::to_device_content_to_type<T>);
}
nhlog::net()->debug(
"Sent verification step: {} in state: {}",
mtx::events::to_string(mtx::events::to_device_content_to_type<T>),
state().toStdString());
}
void handleStartMessage(const mtx::events::msg::KeyVerificationStart &msg, std::string);
//! sends a verification request
void sendVerificationRequest();
//! accepts a verification request
void sendVerificationReady();
//! completes the verification flow();
void sendVerificationDone();
//! accepts a verification
void acceptVerificationRequest();
//! starts the verification flow
void startVerificationRequest();
//! cancels a verification flow
void cancelVerification(DeviceVerificationFlow::Error error_code);
//! sends the verification key
void sendVerificationKey();
//! sends the mac of the keys
void sendVerificationMac();
//! Completes the verification flow
void acceptDevice();
std::string transaction_id;
bool sender;
Type type;
mtx::identifiers::User toClient;
QString deviceId;
// public part of our master key, when trusted or empty
std::string our_trusted_master_key;
mtx::events::msg::SASMethods method = mtx::events::msg::SASMethods::Emoji;
QTimer *timeout = nullptr;
sas_ptr sas;
std::string mac_method;
std::string commitment;
nlohmann::json canonical_json;
std::vector<int> sasList;
UserKeyCache their_keys;
TimelineModel *model_;
mtx::common::Relation relation;
State state_ = PromptStartVerification;
Error error_ = UnknownMethod;
bool isMacVerified = false;
template<typename T>
void send(T msg)
{
if (this->type == DeviceVerificationFlow::Type::ToDevice) {
mtx::requests::ToDeviceMessages<T> body;
msg.transaction_id = this->transaction_id;
body[this->toClient][deviceId.toStdString()] = msg;
http::client()->send_to_device<T>(
this->transaction_id, body, [](mtx::http::RequestErr err) {
if (err)
nhlog::net()->warn("failed to send verification to_device message: {} {}",
err->matrix_error.error,
static_cast<int>(err->status_code));
});
} else if (this->type == DeviceVerificationFlow::Type::RoomMsg && model_) {
if constexpr (!std::is_same_v<T, mtx::events::msg::KeyVerificationRequest>) {
msg.relations.relations.push_back(this->relation);
// Set synthesized to surpress the nheko relation extensions
msg.relations.synthesized = true;
}
(model_)->sendMessageEvent(msg, mtx::events::to_device_content_to_type<T>);
}
nhlog::net()->debug("Sent verification step: {} in state: {}",
mtx::events::to_string(mtx::events::to_device_content_to_type<T>),
state().toStdString());
}
};

@ -16,463 +16,460 @@ using is_detected = typename nheko::detail::detector<nheko::nonesuch, void, Op,
struct IsStateEvent
{
template<class T>
bool operator()(const mtx::events::StateEvent<T> &)
{
return true;
}
template<class T>
bool operator()(const mtx::events::Event<T> &)
{
return false;
}
template<class T>
bool operator()(const mtx::events::StateEvent<T> &)
{
return true;
}
template<class T>
bool operator()(const mtx::events::Event<T> &)
{
return false;
}
};
struct EventMsgType
{
template<class E>
using msgtype_t = decltype(E::msgtype);
template<class T>
mtx::events::MessageType operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<msgtype_t, T>::value) {
if constexpr (std::is_same_v<std::optional<std::string>,
std::remove_cv_t<decltype(e.content.msgtype)>>)
return mtx::events::getMessageType(e.content.msgtype.value());
else if constexpr (std::is_same_v<
std::string,
std::remove_cv_t<decltype(e.content.msgtype)>>)
return mtx::events::getMessageType(e.content.msgtype);
}
return mtx::events::MessageType::Unknown;
template<class E>
using msgtype_t = decltype(E::msgtype);
template<class T>
mtx::events::MessageType operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<msgtype_t, T>::value) {
if constexpr (std::is_same_v<std::optional<std::string>,
std::remove_cv_t<decltype(e.content.msgtype)>>)
return mtx::events::getMessageType(e.content.msgtype.value());
else if constexpr (std::is_same_v<std::string,
std::remove_cv_t<decltype(e.content.msgtype)>>)
return mtx::events::getMessageType(e.content.msgtype);
}
return mtx::events::MessageType::Unknown;
}
};
struct EventRoomName
{
template<class T>
std::string operator()(const T &e)
{
if constexpr (std::is_same_v<mtx::events::StateEvent<mtx::events::state::Name>, T>)
return e.content.name;
return "";
}
template<class T>
std::string operator()(const T &e)
{
if constexpr (std::is_same_v<mtx::events::StateEvent<mtx::events::state::Name>, T>)
return e.content.name;
return "";
}
};
struct EventRoomTopic
{
template<class T>
std::string operator()(const T &e)
{
if constexpr (std::is_same_v<mtx::events::StateEvent<mtx::events::state::Topic>, T>)
return e.content.topic;
return "";
}
template<class T>
std::string operator()(const T &e)
{
if constexpr (std::is_same_v<mtx::events::StateEvent<mtx::events::state::Topic>, T>)
return e.content.topic;
return "";
}
};
struct CallType
{
template<class T>
std::string operator()(const T &e)
{
if constexpr (std::is_same_v<mtx::events::RoomEvent<mtx::events::msg::CallInvite>,
T>) {
const char video[] = "m=video";
const std::string &sdp = e.content.sdp;
return std::search(sdp.cbegin(),
sdp.cend(),
std::cbegin(video),
std::cend(video) - 1,
[](unsigned char c1, unsigned char c2) {
return std::tolower(c1) == std::tolower(c2);
}) != sdp.cend()
? "video"
: "voice";
}
return std::string();
template<class T>
std::string operator()(const T &e)
{
if constexpr (std::is_same_v<mtx::events::RoomEvent<mtx::events::msg::CallInvite>, T>) {
const char video[] = "m=video";
const std::string &sdp = e.content.sdp;
return std::search(sdp.cbegin(),
sdp.cend(),
std::cbegin(video),
std::cend(video) - 1,
[](unsigned char c1, unsigned char c2) {
return std::tolower(c1) == std::tolower(c2);
}) != sdp.cend()
? "video"
: "voice";
}
return std::string();
}
};
struct EventBody
{
template<class C>
using body_t = decltype(C::body);
template<class T>
std::string operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<body_t, T>::value) {
if constexpr (std::is_same_v<std::optional<std::string>,
std::remove_cv_t<decltype(e.content.body)>>)
return e.content.body ? e.content.body.value() : "";
else if constexpr (std::is_same_v<
std::string,
std::remove_cv_t<decltype(e.content.body)>>)
return e.content.body;
}
return "";
template<class C>
using body_t = decltype(C::body);
template<class T>
std::string operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<body_t, T>::value) {
if constexpr (std::is_same_v<std::optional<std::string>,
std::remove_cv_t<decltype(e.content.body)>>)
return e.content.body ? e.content.body.value() : "";
else if constexpr (std::is_same_v<std::string,
std::remove_cv_t<decltype(e.content.body)>>)
return e.content.body;
}
return "";
}
};
struct EventFormattedBody
{
template<class C>
using formatted_body_t = decltype(C::formatted_body);
template<class T>
std::string operator()(const mtx::events::RoomEvent<T> &e)
{
if constexpr (is_detected<formatted_body_t, T>::value) {
if (e.content.format == "org.matrix.custom.html")
return e.content.formatted_body;
}
return "";
template<class C>
using formatted_body_t = decltype(C::formatted_body);
template<class T>
std::string operator()(const mtx::events::RoomEvent<T> &e)
{
if constexpr (is_detected<formatted_body_t, T>::value) {
if (e.content.format == "org.matrix.custom.html")
return e.content.formatted_body;
}
return "";
}
};
struct EventFile
{
template<class Content>
using file_t = decltype(Content::file);
template<class T>
std::optional<mtx::crypto::EncryptedFile> operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<file_t, T>::value)
return e.content.file;
return std::nullopt;
}
template<class Content>
using file_t = decltype(Content::file);
template<class T>
std::optional<mtx::crypto::EncryptedFile> operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<file_t, T>::value)
return e.content.file;
return std::nullopt;
}
};
struct EventUrl
{
template<class Content>
using url_t = decltype(Content::url);
template<class T>
std::string operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<url_t, T>::value) {
if (auto file = EventFile{}(e))
return file->url;
return e.content.url;
}
return "";
template<class Content>
using url_t = decltype(Content::url);
template<class T>
std::string operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<url_t, T>::value) {
if (auto file = EventFile{}(e))
return file->url;
return e.content.url;
}
return "";
}
};
struct EventThumbnailUrl
{
template<class Content>
using thumbnail_url_t = decltype(Content::info.thumbnail_url);
template<class T>
std::string operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<thumbnail_url_t, T>::value) {
return e.content.info.thumbnail_url;
}
return "";
template<class Content>
using thumbnail_url_t = decltype(Content::info.thumbnail_url);
template<class T>
std::string operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<thumbnail_url_t, T>::value) {
return e.content.info.thumbnail_url;
}
return "";
}
};
struct EventBlurhash
{
template<class Content>
using blurhash_t = decltype(Content::info.blurhash);
template<class T>
std::string operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<blurhash_t, T>::value) {
return e.content.info.blurhash;
}
return "";
template<class Content>
using blurhash_t = decltype(Content::info.blurhash);
template<class T>
std::string operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<blurhash_t, T>::value) {
return e.content.info.blurhash;
}
return "";
}
};
struct EventFilename
{
template<class T>
std::string operator()(const mtx::events::Event<T> &)
{
return "";
}
std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::Audio> &e)
{
// body may be the original filename
return e.content.body;
}
std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::Video> &e)
{
// body may be the original filename
return e.content.body;
}
std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::Image> &e)
{
// body may be the original filename
return e.content.body;
}
std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::File> &e)
{
// body may be the original filename
if (!e.content.filename.empty())
return e.content.filename;
return e.content.body;
}
template<class T>
std::string operator()(const mtx::events::Event<T> &)
{
return "";
}
std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::Audio> &e)
{
// body may be the original filename
return e.content.body;
}
std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::Video> &e)
{
// body may be the original filename
return e.content.body;
}
std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::Image> &e)
{
// body may be the original filename
return e.content.body;
}
std::string operator()(const mtx::events::RoomEvent<mtx::events::msg::File> &e)
{
// body may be the original filename
if (!e.content.filename.empty())
return e.content.filename;
return e.content.body;
}
};
struct EventMimeType
{
template<class Content>
using mimetype_t = decltype(Content::info.mimetype);
template<class T>
std::string operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<mimetype_t, T>::value) {
return e.content.info.mimetype;
}
return "";
template<class Content>
using mimetype_t = decltype(Content::info.mimetype);
template<class T>
std::string operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<mimetype_t, T>::value) {
return e.content.info.mimetype;
}
return "";
}
};
struct EventFilesize
{
template<class Content>
using filesize_t = decltype(Content::info.size);
template<class T>
int64_t operator()(const mtx::events::RoomEvent<T> &e)
{
if constexpr (is_detected<filesize_t, T>::value) {
return e.content.info.size;
}
return 0;
template<class Content>
using filesize_t = decltype(Content::info.size);
template<class T>
int64_t operator()(const mtx::events::RoomEvent<T> &e)
{
if constexpr (is_detected<filesize_t, T>::value) {
return e.content.info.size;
}
return 0;
}
};
struct EventRelations
{
template<class Content>
using related_ev_id_t = decltype(Content::relations);
template<class T>
mtx::common::Relations operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<related_ev_id_t, T>::value) {
return e.content.relations;
}
return {};
template<class Content>
using related_ev_id_t = decltype(Content::relations);
template<class T>
mtx::common::Relations operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<related_ev_id_t, T>::value) {
return e.content.relations;
}
return {};
}
};
struct SetEventRelations
{
mtx::common::Relations new_relations;
template<class Content>
using related_ev_id_t = decltype(Content::relations);
template<class T>
void operator()(mtx::events::Event<T> &e)
{
if constexpr (is_detected<related_ev_id_t, T>::value) {
e.content.relations = std::move(new_relations);
}
mtx::common::Relations new_relations;
template<class Content>
using related_ev_id_t = decltype(Content::relations);
template<class T>
void operator()(mtx::events::Event<T> &e)
{
if constexpr (is_detected<related_ev_id_t, T>::value) {
e.content.relations = std::move(new_relations);
}
}
};
struct EventTransactionId
{
template<class T>
std::string operator()(const mtx::events::RoomEvent<T> &e)
{
return e.unsigned_data.transaction_id;
}
template<class T>
std::string operator()(const mtx::events::Event<T> &e)
{
return e.unsigned_data.transaction_id;
}
template<class T>
std::string operator()(const mtx::events::RoomEvent<T> &e)
{
return e.unsigned_data.transaction_id;
}
template<class T>
std::string operator()(const mtx::events::Event<T> &e)
{
return e.unsigned_data.transaction_id;
}
};
struct EventMediaHeight
{
template<class Content>
using h_t = decltype(Content::info.h);
template<class T>
uint64_t operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<h_t, T>::value) {
return e.content.info.h;
}
return -1;
template<class Content>
using h_t = decltype(Content::info.h);
template<class T>
uint64_t operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<h_t, T>::value) {
return e.content.info.h;
}
return -1;
}
};
struct EventMediaWidth
{
template<class Content>
using w_t = decltype(Content::info.w);
template<class T>
uint64_t operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<w_t, T>::value) {
return e.content.info.w;
}
return -1;
template<class Content>
using w_t = decltype(Content::info.w);
template<class T>
uint64_t operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<w_t, T>::value) {
return e.content.info.w;
}
return -1;
}
};
template<class T>
double
eventPropHeight(const mtx::events::RoomEvent<T> &e)
{
auto w = eventWidth(e);
if (w == 0)
w = 1;
auto w = eventWidth(e);
if (w == 0)
w = 1;
double prop = eventHeight(e) / (double)w;
double prop = eventHeight(e) / (double)w;
return prop > 0 ? prop : 1.;
return prop > 0 ? prop : 1.;
}
}
std::string
mtx::accessors::event_id(const mtx::events::collections::TimelineEvents &event)
{
return std::visit([](const auto e) { return e.event_id; }, event);
return std::visit([](const auto e) { return e.event_id; }, event);
}
std::string
mtx::accessors::room_id(const mtx::events::collections::TimelineEvents &event)
{
return std::visit([](const auto e) { return e.room_id; }, event);
return std::visit([](const auto e) { return e.room_id; }, event);
}
std::string
mtx::accessors::sender(const mtx::events::collections::TimelineEvents &event)
{
return std::visit([](const auto e) { return e.sender; }, event);
return std::visit([](const auto e) { return e.sender; }, event);
}
QDateTime
mtx::accessors::origin_server_ts(const mtx::events::collections::TimelineEvents &event)
{
return QDateTime::fromMSecsSinceEpoch(
std::visit([](const auto e) { return e.origin_server_ts; }, event));
return QDateTime::fromMSecsSinceEpoch(
std::visit([](const auto e) { return e.origin_server_ts; }, event));
}
std::string
mtx::accessors::filename(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventFilename{}, event);
return std::visit(EventFilename{}, event);
}
mtx::events::MessageType
mtx::accessors::msg_type(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventMsgType{}, event);
return std::visit(EventMsgType{}, event);
}
std::string
mtx::accessors::room_name(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventRoomName{}, event);
return std::visit(EventRoomName{}, event);
}
std::string
mtx::accessors::room_topic(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventRoomTopic{}, event);
return std::visit(EventRoomTopic{}, event);
}
std::string
mtx::accessors::call_type(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(CallType{}, event);
return std::visit(CallType{}, event);
}
std::string
mtx::accessors::body(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventBody{}, event);
return std::visit(EventBody{}, event);
}
std::string
mtx::accessors::formatted_body(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventFormattedBody{}, event);
return std::visit(EventFormattedBody{}, event);
}
QString
mtx::accessors::formattedBodyWithFallback(const mtx::events::collections::TimelineEvents &event)
{
auto formatted = formatted_body(event);
if (!formatted.empty())
return QString::fromStdString(formatted);
else
return QString::fromStdString(body(event)).toHtmlEscaped().replace("\n", "<br>");
auto formatted = formatted_body(event);
if (!formatted.empty())
return QString::fromStdString(formatted);
else
return QString::fromStdString(body(event)).toHtmlEscaped().replace("\n", "<br>");
}
std::optional<mtx::crypto::EncryptedFile>
mtx::accessors::file(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventFile{}, event);
return std::visit(EventFile{}, event);
}
std::string
mtx::accessors::url(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventUrl{}, event);
return std::visit(EventUrl{}, event);
}
std::string
mtx::accessors::thumbnail_url(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventThumbnailUrl{}, event);
return std::visit(EventThumbnailUrl{}, event);
}
std::string
mtx::accessors::blurhash(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventBlurhash{}, event);
return std::visit(EventBlurhash{}, event);
}
std::string
mtx::accessors::mimetype(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventMimeType{}, event);
return std::visit(EventMimeType{}, event);
}
mtx::common::Relations
mtx::accessors::relations(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventRelations{}, event);
return std::visit(EventRelations{}, event);
}
void
mtx::accessors::set_relations(mtx::events::collections::TimelineEvents &event,
mtx::common::Relations relations)
{
std::visit(SetEventRelations{std::move(relations)}, event);
std::visit(SetEventRelations{std::move(relations)}, event);
}
std::string
mtx::accessors::transaction_id(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventTransactionId{}, event);
return std::visit(EventTransactionId{}, event);
}
int64_t
mtx::accessors::filesize(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventFilesize{}, event);
return std::visit(EventFilesize{}, event);
}
uint64_t
mtx::accessors::media_height(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventMediaHeight{}, event);
return std::visit(EventMediaHeight{}, event);
}
uint64_t
mtx::accessors::media_width(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventMediaWidth{}, event);
return std::visit(EventMediaWidth{}, event);
}
nlohmann::json
mtx::accessors::serialize_event(const mtx::events::collections::TimelineEvents &event)
{
return std::visit([](const auto &e) { return nlohmann::json(e); }, event);
return std::visit([](const auto &e) { return nlohmann::json(e); }, event);
}
bool
mtx::accessors::is_state_event(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(IsStateEvent{}, event);
return std::visit(IsStateEvent{}, event);
}

@ -14,24 +14,24 @@
namespace nheko {
struct nonesuch
{
~nonesuch() = delete;
nonesuch(nonesuch const &) = delete;
void operator=(nonesuch const &) = delete;
~nonesuch() = delete;
nonesuch(nonesuch const &) = delete;
void operator=(nonesuch const &) = delete;
};
namespace detail {
template<class Default, class AlwaysVoid, template<class...> class Op, class... Args>
struct detector
{
using value_t = std::false_type;
using type = Default;
using value_t = std::false_type;
using type = Default;
};
template<class Default, template<class...> class Op, class... Args>
struct detector<Default, std::void_t<Op<Args...>>, Op, Args...>
{
using value_t = std::true_type;
using type = Op<Args...>;
using value_t = std::true_type;
using type = Op<Args...>;
};
} // namespace detail

@ -13,82 +13,81 @@ ImagePackListModel::ImagePackListModel(const std::string &roomId, QObject *paren
: QAbstractListModel(parent)
, room_id(roomId)
{
auto packs_ = cache::client()->getImagePacks(room_id, std::nullopt);
auto packs_ = cache::client()->getImagePacks(room_id, std::nullopt);
for (const auto &pack : packs_) {
packs.push_back(
QSharedPointer<SingleImagePackModel>(new SingleImagePackModel(pack)));
}
for (const auto &pack : packs_) {
packs.push_back(QSharedPointer<SingleImagePackModel>(new SingleImagePackModel(pack)));
}
}
int
ImagePackListModel::rowCount(const QModelIndex &) const
{
return (int)packs.size();
return (int)packs.size();
}
QHash<int, QByteArray>
ImagePackListModel::roleNames() const
{
return {
{Roles::DisplayName, "displayName"},
{Roles::AvatarUrl, "avatarUrl"},
{Roles::FromAccountData, "fromAccountData"},
{Roles::FromCurrentRoom, "fromCurrentRoom"},
{Roles::StateKey, "statekey"},
{Roles::RoomId, "roomid"},
};
return {
{Roles::DisplayName, "displayName"},
{Roles::AvatarUrl, "avatarUrl"},
{Roles::FromAccountData, "fromAccountData"},
{Roles::FromCurrentRoom, "fromCurrentRoom"},
{Roles::StateKey, "statekey"},
{Roles::RoomId, "roomid"},
};
}
QVariant
ImagePackListModel::data(const QModelIndex &index, int role) const
{
if (hasIndex(index.row(), index.column(), index.parent())) {
const auto &pack = packs.at(index.row());
switch (role) {
case Roles::DisplayName:
return pack->packname();
case Roles::AvatarUrl:
return pack->avatarUrl();
case Roles::FromAccountData:
return pack->roomid().isEmpty();
case Roles::FromCurrentRoom:
return pack->roomid().toStdString() == this->room_id;
case Roles::StateKey:
return pack->statekey();
case Roles::RoomId:
return pack->roomid();
default:
return {};
}
if (hasIndex(index.row(), index.column(), index.parent())) {
const auto &pack = packs.at(index.row());
switch (role) {
case Roles::DisplayName:
return pack->packname();
case Roles::AvatarUrl:
return pack->avatarUrl();
case Roles::FromAccountData:
return pack->roomid().isEmpty();
case Roles::FromCurrentRoom:
return pack->roomid().toStdString() == this->room_id;
case Roles::StateKey:
return pack->statekey();
case Roles::RoomId:
return pack->roomid();
default:
return {};
}
return {};
}
return {};
}
SingleImagePackModel *
ImagePackListModel::packAt(int row)
{
if (row < 0 || static_cast<size_t>(row) >= packs.size())
return {};
auto e = packs.at(row).get();
QQmlEngine::setObjectOwnership(e, QQmlEngine::CppOwnership);
return e;
if (row < 0 || static_cast<size_t>(row) >= packs.size())
return {};
auto e = packs.at(row).get();
QQmlEngine::setObjectOwnership(e, QQmlEngine::CppOwnership);
return e;
}
SingleImagePackModel *
ImagePackListModel::newPack(bool inRoom)
{
ImagePackInfo info{};
if (inRoom)
info.source_room = room_id;
return new SingleImagePackModel(info);
ImagePackInfo info{};
if (inRoom)
info.source_room = room_id;
return new SingleImagePackModel(info);
}
bool
ImagePackListModel::containsAccountPack() const
{
for (const auto &p : packs)
if (p->roomid().isEmpty())
return true;
return false;
for (const auto &p : packs)
if (p->roomid().isEmpty())
return true;
return false;
}

@ -11,31 +11,31 @@
class SingleImagePackModel;
class ImagePackListModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(bool containsAccountPack READ containsAccountPack CONSTANT)
Q_OBJECT
Q_PROPERTY(bool containsAccountPack READ containsAccountPack CONSTANT)
public:
enum Roles
{
DisplayName = Qt::UserRole,
AvatarUrl,
FromAccountData,
FromCurrentRoom,
StateKey,
RoomId,
};
ImagePackListModel(const std::string &roomId, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
Q_INVOKABLE SingleImagePackModel *packAt(int row);
Q_INVOKABLE SingleImagePackModel *newPack(bool inRoom);
bool containsAccountPack() const;
enum Roles
{
DisplayName = Qt::UserRole,
AvatarUrl,
FromAccountData,
FromCurrentRoom,
StateKey,
RoomId,
};
ImagePackListModel(const std::string &roomId, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
Q_INVOKABLE SingleImagePackModel *packAt(int row);
Q_INVOKABLE SingleImagePackModel *newPack(bool inRoom);
bool containsAccountPack() const;
private:
std::string room_id;
std::string room_id;
std::vector<QSharedPointer<SingleImagePackModel>> packs;
std::vector<QSharedPointer<SingleImagePackModel>> packs;
};

@ -16,69 +16,68 @@ InviteesModel::InviteesModel(QObject *parent)
void
InviteesModel::addUser(QString mxid)
{
beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count());
beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count());
auto invitee = new Invitee{mxid, this};
auto indexOfInvitee = invitees_.count();
connect(invitee, &Invitee::userInfoLoaded, this, [this, indexOfInvitee]() {
emit dataChanged(index(indexOfInvitee), index(indexOfInvitee));
});
auto invitee = new Invitee{mxid, this};
auto indexOfInvitee = invitees_.count();
connect(invitee, &Invitee::userInfoLoaded, this, [this, indexOfInvitee]() {
emit dataChanged(index(indexOfInvitee), index(indexOfInvitee));
});
invitees_.push_back(invitee);
invitees_.push_back(invitee);
endInsertRows();
emit countChanged();
endInsertRows();
emit countChanged();
}
QHash<int, QByteArray>
InviteesModel::roleNames() const
{
return {{Mxid, "mxid"}, {DisplayName, "displayName"}, {AvatarUrl, "avatarUrl"}};
return {{Mxid, "mxid"}, {DisplayName, "displayName"}, {AvatarUrl, "avatarUrl"}};
}
QVariant
InviteesModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() >= (int)invitees_.size() || index.row() < 0)
return {};
if (!index.isValid() || index.row() >= (int)invitees_.size() || index.row() < 0)
return {};
switch (role) {
case Mxid:
return invitees_[index.row()]->mxid_;
case DisplayName:
return invitees_[index.row()]->displayName_;
case AvatarUrl:
return invitees_[index.row()]->avatarUrl_;
default:
return {};
}
switch (role) {
case Mxid:
return invitees_[index.row()]->mxid_;
case DisplayName:
return invitees_[index.row()]->displayName_;
case AvatarUrl:
return invitees_[index.row()]->avatarUrl_;
default:
return {};
}
}
QStringList
InviteesModel::mxids()
{
QStringList mxidList;
for (int i = 0; i < invitees_.length(); ++i)
mxidList.push_back(invitees_[i]->mxid_);
return mxidList;
QStringList mxidList;
for (int i = 0; i < invitees_.length(); ++i)
mxidList.push_back(invitees_[i]->mxid_);
return mxidList;
}
Invitee::Invitee(const QString &mxid, QObject *parent)
: QObject{parent}
, mxid_{mxid}
{
http::client()->get_profile(
mxid_.toStdString(),
[this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
if (err) {
nhlog::net()->warn("failed to retrieve profile info");
emit userInfoLoaded();
return;
}
http::client()->get_profile(
mxid_.toStdString(), [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
if (err) {
nhlog::net()->warn("failed to retrieve profile info");
emit userInfoLoaded();
return;
}
displayName_ = QString::fromStdString(res.display_name);
avatarUrl_ = QString::fromStdString(res.avatar_url);
displayName_ = QString::fromStdString(res.display_name);
avatarUrl_ = QString::fromStdString(res.avatar_url);
emit userInfoLoaded();
});
emit userInfoLoaded();
});
}

@ -10,54 +10,54 @@
class Invitee : public QObject
{
Q_OBJECT
Q_OBJECT
public:
Invitee(const QString &mxid, QObject *parent = nullptr);
Invitee(const QString &mxid, QObject *parent = nullptr);
signals:
void userInfoLoaded();
void userInfoLoaded();
private:
const QString mxid_;
QString displayName_;
QString avatarUrl_;
const QString mxid_;
QString displayName_;
QString avatarUrl_;
friend class InviteesModel;
friend class InviteesModel;
};
class InviteesModel : public QAbstractListModel
{
Q_OBJECT
Q_OBJECT
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
public:
enum Roles
{
Mxid,
DisplayName,
AvatarUrl,
};
enum Roles
{
Mxid,
DisplayName,
AvatarUrl,
};
InviteesModel(QObject *parent = nullptr);
InviteesModel(QObject *parent = nullptr);
Q_INVOKABLE void addUser(QString mxid);
Q_INVOKABLE void addUser(QString mxid);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex & = QModelIndex()) const override
{
return (int)invitees_.size();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QStringList mxids();
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex & = QModelIndex()) const override
{
return (int)invitees_.size();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QStringList mxids();
signals:
void accept();
void countChanged();
void accept();
void countChanged();
private:
QVector<Invitee *> invitees_;
QVector<Invitee *> invitees_;
};
#endif // INVITEESMODEL_H

@ -22,20 +22,20 @@
static QPixmap
clipRadius(QPixmap img, double radius)
{
QPixmap out(img.size());
out.fill(Qt::transparent);
QPixmap out(img.size());
out.fill(Qt::transparent);
QPainter painter(&out);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
QPainter painter(&out);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
QPainterPath ppath;
ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize);
QPainterPath ppath;
ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize);
painter.setClipPath(ppath);
painter.drawPixmap(img.rect(), img);
painter.setClipPath(ppath);
painter.drawPixmap(img.rect(), img);
return out;
return out;
}
JdenticonResponse::JdenticonResponse(const QString &key,
@ -49,64 +49,64 @@ JdenticonResponse::JdenticonResponse(const QString &key,
, m_pixmap{m_requestedSize}
, jdenticonInterface_{Jdenticon::getJdenticonInterface()}
{
setAutoDelete(false);
setAutoDelete(false);
}
void
JdenticonResponse::run()
{
m_pixmap.fill(Qt::transparent);
QPainter painter;
painter.begin(&m_pixmap);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
try {
QSvgRenderer renderer{
jdenticonInterface_->generate(m_key, m_requestedSize.width()).toUtf8()};
renderer.render(&painter);
} catch (std::exception &e) {
nhlog::ui()->error(
"caught {} in jdenticonprovider, key '{}'", e.what(), m_key.toStdString());
}
m_pixmap.fill(Qt::transparent);
QPainter painter;
painter.begin(&m_pixmap);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
try {
QSvgRenderer renderer{
jdenticonInterface_->generate(m_key, m_requestedSize.width()).toUtf8()};
renderer.render(&painter);
} catch (std::exception &e) {
nhlog::ui()->error(
"caught {} in jdenticonprovider, key '{}'", e.what(), m_key.toStdString());
}
painter.end();
painter.end();
m_pixmap = clipRadius(m_pixmap, m_radius);
m_pixmap = clipRadius(m_pixmap, m_radius);
emit finished();
emit finished();
}
namespace Jdenticon {
JdenticonInterface *
getJdenticonInterface()
{
static JdenticonInterface *interface = nullptr;
static bool interfaceExists{true};
if (interface == nullptr && interfaceExists) {
QDir pluginsDir(qApp->applicationDirPath());
bool plugins = pluginsDir.cd("plugins");
if (plugins) {
for (const QString &fileName : pluginsDir.entryList(QDir::Files)) {
QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
QObject *plugin = pluginLoader.instance();
if (plugin) {
interface = qobject_cast<JdenticonInterface *>(plugin);
if (interface) {
nhlog::ui()->info("Loaded jdenticon plugin.");
break;
}
}
}
} else {
nhlog::ui()->info("jdenticon plugin not found.");
interfaceExists = false;
static JdenticonInterface *interface = nullptr;
static bool interfaceExists{true};
if (interface == nullptr && interfaceExists) {
QDir pluginsDir(qApp->applicationDirPath());
bool plugins = pluginsDir.cd("plugins");
if (plugins) {
for (const QString &fileName : pluginsDir.entryList(QDir::Files)) {
QPluginLoader pluginLoader(pluginsDir.absoluteFilePath(fileName));
QObject *plugin = pluginLoader.instance();
if (plugin) {
interface = qobject_cast<JdenticonInterface *>(plugin);
if (interface) {
nhlog::ui()->info("Loaded jdenticon plugin.");
break;
}
}
}
} else {
nhlog::ui()->info("jdenticon plugin not found.");
interfaceExists = false;
}
}
return interface;
return interface;
}
}

@ -23,59 +23,58 @@ class JdenticonResponse
, public QRunnable
{
public:
JdenticonResponse(const QString &key, bool crop, double radius, const QSize &requestedSize);
JdenticonResponse(const QString &key, bool crop, double radius, const QSize &requestedSize);
QQuickTextureFactory *textureFactory() const override
{
return QQuickTextureFactory::textureFactoryForImage(m_pixmap.toImage());
}
QQuickTextureFactory *textureFactory() const override
{
return QQuickTextureFactory::textureFactoryForImage(m_pixmap.toImage());
}
void run() override;
void run() override;
QString m_key;
bool m_crop;
double m_radius;
QSize m_requestedSize;
QPixmap m_pixmap;
JdenticonInterface *jdenticonInterface_ = nullptr;
QString m_key;
bool m_crop;
double m_radius;
QSize m_requestedSize;
QPixmap m_pixmap;
JdenticonInterface *jdenticonInterface_ = nullptr;
};
class JdenticonProvider
: public QObject
, public QQuickAsyncImageProvider
{
Q_OBJECT
Q_OBJECT
public:
static bool isAvailable() { return Jdenticon::getJdenticonInterface() != nullptr; }
static bool isAvailable() { return Jdenticon::getJdenticonInterface() != nullptr; }
public slots:
QQuickImageResponse *requestImageResponse(const QString &id,
const QSize &requestedSize) override
{
auto id_ = id;
bool crop = true;
double radius = 0;
auto queryStart = id.lastIndexOf('?');
if (queryStart != -1) {
id_ = id.left(queryStart);
auto query = id.midRef(queryStart + 1);
auto queryBits = query.split('&');
for (auto b : queryBits) {
if (b.startsWith("radius=")) {
radius = b.mid(7).toDouble();
}
}
QQuickImageResponse *requestImageResponse(const QString &id,
const QSize &requestedSize) override
{
auto id_ = id;
bool crop = true;
double radius = 0;
auto queryStart = id.lastIndexOf('?');
if (queryStart != -1) {
id_ = id.left(queryStart);
auto query = id.midRef(queryStart + 1);
auto queryBits = query.split('&');
for (auto b : queryBits) {
if (b.startsWith("radius=")) {
radius = b.mid(7).toDouble();
}
JdenticonResponse *response =
new JdenticonResponse(id_, crop, radius, requestedSize);
pool.start(response);
return response;
}
}
JdenticonResponse *response = new JdenticonResponse(id_, crop, radius, requestedSize);
pool.start(response);
return response;
}
private:
QThreadPool pool;
QThreadPool pool;
};

@ -25,35 +25,35 @@ constexpr auto MAX_LOG_FILES = 3;
void
qmlMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
std::string localMsg = msg.toStdString();
const char *file = context.file ? context.file : "";
const char *function = context.function ? context.function : "";
if (
// The default style has the point size set. If you use pixel size anywhere, you get
// that warning, which is useless, since sometimes you need the pixel size to match the
// text to the size of the outer element for example. This is done in the avatar and
// without that you get one warning for every Avatar displayed, which is stupid!
msg.endsWith(QStringLiteral("Both point size and pixel size set. Using pixel size.")))
return;
switch (type) {
case QtDebugMsg:
nhlog::qml()->debug("{} ({}:{}, {})", localMsg, file, context.line, function);
break;
case QtInfoMsg:
nhlog::qml()->info("{} ({}:{}, {})", localMsg, file, context.line, function);
break;
case QtWarningMsg:
nhlog::qml()->warn("{} ({}:{}, {})", localMsg, file, context.line, function);
break;
case QtCriticalMsg:
nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function);
break;
case QtFatalMsg:
nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function);
break;
}
std::string localMsg = msg.toStdString();
const char *file = context.file ? context.file : "";
const char *function = context.function ? context.function : "";
if (
// The default style has the point size set. If you use pixel size anywhere, you get
// that warning, which is useless, since sometimes you need the pixel size to match the
// text to the size of the outer element for example. This is done in the avatar and
// without that you get one warning for every Avatar displayed, which is stupid!
msg.endsWith(QStringLiteral("Both point size and pixel size set. Using pixel size.")))
return;
switch (type) {
case QtDebugMsg:
nhlog::qml()->debug("{} ({}:{}, {})", localMsg, file, context.line, function);
break;
case QtInfoMsg:
nhlog::qml()->info("{} ({}:{}, {})", localMsg, file, context.line, function);
break;
case QtWarningMsg:
nhlog::qml()->warn("{} ({}:{}, {})", localMsg, file, context.line, function);
break;
case QtCriticalMsg:
nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function);
break;
case QtFatalMsg:
nhlog::qml()->critical("{} ({}:{}, {})", localMsg, file, context.line, function);
break;
}
}
}
@ -63,60 +63,59 @@ bool enable_debug_log_from_commandline = false;
void
init(const std::string &file_path)
{
auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
file_path, MAX_FILE_SIZE, MAX_LOG_FILES);
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
std::vector<spdlog::sink_ptr> sinks;
sinks.push_back(file_sink);
sinks.push_back(console_sink);
net_logger = std::make_shared<spdlog::logger>("net", std::begin(sinks), std::end(sinks));
ui_logger = std::make_shared<spdlog::logger>("ui", std::begin(sinks), std::end(sinks));
db_logger = std::make_shared<spdlog::logger>("db", std::begin(sinks), std::end(sinks));
crypto_logger =
std::make_shared<spdlog::logger>("crypto", std::begin(sinks), std::end(sinks));
qml_logger = std::make_shared<spdlog::logger>("qml", std::begin(sinks), std::end(sinks));
if (nheko::enable_debug_log || enable_debug_log_from_commandline) {
db_logger->set_level(spdlog::level::trace);
ui_logger->set_level(spdlog::level::trace);
crypto_logger->set_level(spdlog::level::trace);
net_logger->set_level(spdlog::level::trace);
qml_logger->set_level(spdlog::level::trace);
}
qInstallMessageHandler(qmlMessageHandler);
auto file_sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
file_path, MAX_FILE_SIZE, MAX_LOG_FILES);
auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
std::vector<spdlog::sink_ptr> sinks;
sinks.push_back(file_sink);
sinks.push_back(console_sink);
net_logger = std::make_shared<spdlog::logger>("net", std::begin(sinks), std::end(sinks));
ui_logger = std::make_shared<spdlog::logger>("ui", std::begin(sinks), std::end(sinks));
db_logger = std::make_shared<spdlog::logger>("db", std::begin(sinks), std::end(sinks));
crypto_logger = std::make_shared<spdlog::logger>("crypto", std::begin(sinks), std::end(sinks));
qml_logger = std::make_shared<spdlog::logger>("qml", std::begin(sinks), std::end(sinks));
if (nheko::enable_debug_log || enable_debug_log_from_commandline) {
db_logger->set_level(spdlog::level::trace);
ui_logger->set_level(spdlog::level::trace);
crypto_logger->set_level(spdlog::level::trace);
net_logger->set_level(spdlog::level::trace);
qml_logger->set_level(spdlog::level::trace);
}
qInstallMessageHandler(qmlMessageHandler);
}
std::shared_ptr<spdlog::logger>
ui()
{
return ui_logger;
return ui_logger;
}
std::shared_ptr<spdlog::logger>
net()
{
return net_logger;
return net_logger;
}
std::shared_ptr<spdlog::logger>
db()
{
return db_logger;
return db_logger;
}
std::shared_ptr<spdlog::logger>
crypto()
{
return crypto_logger;
return crypto_logger;
}
std::shared_ptr<spdlog::logger>
qml()
{
return qml_logger;
return qml_logger;
}
}

@ -34,477 +34,468 @@ LoginPage::LoginPage(QWidget *parent)
: QWidget(parent)
, inferredServerAddress_()
{
qRegisterMetaType<LoginPage::LoginMethod>("LoginPage::LoginMethod");
qRegisterMetaType<LoginPage::LoginMethod>("LoginPage::LoginMethod");
top_layout_ = new QVBoxLayout();
top_layout_ = new QVBoxLayout();
top_bar_layout_ = new QHBoxLayout();
top_bar_layout_->setSpacing(0);
top_bar_layout_->setMargin(0);
back_button_ = new FlatButton(this);
back_button_->setMinimumSize(QSize(30, 30));
top_bar_layout_ = new QHBoxLayout();
top_bar_layout_->setSpacing(0);
top_bar_layout_->setMargin(0);
back_button_ = new FlatButton(this);
back_button_->setMinimumSize(QSize(30, 30));
top_bar_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter);
top_bar_layout_->addStretch(1);
QIcon icon;
icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png");
back_button_->setIcon(icon);
back_button_->setIconSize(QSize(32, 32));
QIcon logo;
logo.addFile(":/logos/login.png");
logo_ = new QLabel(this);
logo_->setPixmap(logo.pixmap(128));
logo_layout_ = new QHBoxLayout();
logo_layout_->setContentsMargins(0, 0, 0, 20);
logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter);
form_wrapper_ = new QHBoxLayout();
form_widget_ = new QWidget();
form_widget_->setMinimumSize(QSize(350, 200));
form_layout_ = new QVBoxLayout();
form_layout_->setSpacing(20);
form_layout_->setContentsMargins(0, 0, 0, 30);
form_widget_->setLayout(form_layout_);
form_wrapper_->addStretch(1);
form_wrapper_->addWidget(form_widget_);
form_wrapper_->addStretch(1);
matrixid_input_ = new TextField(this);
matrixid_input_->setLabel(tr("Matrix ID"));
matrixid_input_->setRegexp(QRegularExpression("@.+?:.{3,}"));
matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org"));
matrixid_input_->setToolTip(
tr("Your login name. A mxid should start with @ followed by the user id. After the user "
"id you need to include your server name after a :.\nYou can also put your homeserver "
"address there, if your server doesn't support .well-known lookup.\nExample: "
"@user:server.my\nIf Nheko fails to discover your homeserver, it will show you a "
"field to enter the server manually."));
spinner_ = new LoadingIndicator(this);
spinner_->setFixedHeight(40);
spinner_->setFixedWidth(40);
spinner_->hide();
errorIcon_ = new QLabel(this);
errorIcon_->setPixmap(QPixmap(":/icons/icons/error.png"));
errorIcon_->hide();
matrixidLayout_ = new QHBoxLayout();
matrixidLayout_->addWidget(matrixid_input_, 0, Qt::AlignVCenter);
QFont font;
error_matrixid_label_ = new QLabel(this);
error_matrixid_label_->setFont(font);
error_matrixid_label_->setWordWrap(true);
password_input_ = new TextField(this);
password_input_->setLabel(tr("Password"));
password_input_->setEchoMode(QLineEdit::Password);
password_input_->setToolTip(tr("Your password."));
deviceName_ = new TextField(this);
deviceName_->setLabel(tr("Device name"));
deviceName_->setToolTip(
tr("A name for this device, which will be shown to others, when verifying your devices. "
"If none is provided a default is used."));
serverInput_ = new TextField(this);
serverInput_->setLabel(tr("Homeserver address"));
serverInput_->setPlaceholderText(tr("server.my:8787"));
serverInput_->setToolTip(tr("The address that can be used to contact you homeservers "
"client API.\nExample: https://server.my:8787"));
serverInput_->hide();
serverLayout_ = new QHBoxLayout();
serverLayout_->addWidget(serverInput_, 0, Qt::AlignVCenter);
form_layout_->addLayout(matrixidLayout_);
form_layout_->addWidget(error_matrixid_label_, 0, Qt::AlignHCenter);
form_layout_->addWidget(password_input_);
form_layout_->addWidget(deviceName_, Qt::AlignHCenter);
form_layout_->addLayout(serverLayout_);
error_matrixid_label_->hide();
button_layout_ = new QHBoxLayout();
button_layout_->setSpacing(20);
button_layout_->setContentsMargins(0, 0, 0, 30);
login_button_ = new RaisedButton(tr("LOGIN"), this);
login_button_->setMinimumSize(150, 65);
login_button_->setFontSize(20);
login_button_->setCornerRadius(3);
sso_login_button_ = new RaisedButton(tr("SSO LOGIN"), this);
sso_login_button_->setMinimumSize(150, 65);
sso_login_button_->setFontSize(20);
sso_login_button_->setCornerRadius(3);
sso_login_button_->setVisible(false);
button_layout_->addStretch(1);
button_layout_->addWidget(login_button_);
button_layout_->addWidget(sso_login_button_);
button_layout_->addStretch(1);
error_label_ = new QLabel(this);
error_label_->setFont(font);
error_label_->setWordWrap(true);
top_layout_->addLayout(top_bar_layout_);
top_layout_->addStretch(1);
top_layout_->addLayout(logo_layout_);
top_layout_->addLayout(form_wrapper_);
top_layout_->addStretch(1);
top_layout_->addLayout(button_layout_);
top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter);
top_layout_->addStretch(1);
setLayout(top_layout_);
connect(this, &LoginPage::versionOkCb, this, &LoginPage::versionOk, Qt::QueuedConnection);
connect(
this, &LoginPage::versionErrorCb, this, &LoginPage::versionError, Qt::QueuedConnection);
connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
connect(login_button_, &RaisedButton::clicked, this, [this]() {
onLoginButtonClicked(passwordSupported ? LoginMethod::Password : LoginMethod::SSO);
});
connect(sso_login_button_, &RaisedButton::clicked, this, [this]() {
onLoginButtonClicked(LoginMethod::SSO);
});
connect(this,
&LoginPage::showErrorMessage,
this,
static_cast<void (LoginPage::*)(QLabel *, const QString &)>(&LoginPage::showError),
Qt::QueuedConnection);
connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
connect(deviceName_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered()));
connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered()));
top_bar_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter);
top_bar_layout_->addStretch(1);
QIcon icon;
icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png");
back_button_->setIcon(icon);
back_button_->setIconSize(QSize(32, 32));
QIcon logo;
logo.addFile(":/logos/login.png");
logo_ = new QLabel(this);
logo_->setPixmap(logo.pixmap(128));
logo_layout_ = new QHBoxLayout();
logo_layout_->setContentsMargins(0, 0, 0, 20);
logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter);
form_wrapper_ = new QHBoxLayout();
form_widget_ = new QWidget();
form_widget_->setMinimumSize(QSize(350, 200));
form_layout_ = new QVBoxLayout();
form_layout_->setSpacing(20);
form_layout_->setContentsMargins(0, 0, 0, 30);
form_widget_->setLayout(form_layout_);
form_wrapper_->addStretch(1);
form_wrapper_->addWidget(form_widget_);
form_wrapper_->addStretch(1);
matrixid_input_ = new TextField(this);
matrixid_input_->setLabel(tr("Matrix ID"));
matrixid_input_->setRegexp(QRegularExpression("@.+?:.{3,}"));
matrixid_input_->setPlaceholderText(tr("e.g @joe:matrix.org"));
matrixid_input_->setToolTip(
tr("Your login name. A mxid should start with @ followed by the user id. After the user "
"id you need to include your server name after a :.\nYou can also put your homeserver "
"address there, if your server doesn't support .well-known lookup.\nExample: "
"@user:server.my\nIf Nheko fails to discover your homeserver, it will show you a "
"field to enter the server manually."));
spinner_ = new LoadingIndicator(this);
spinner_->setFixedHeight(40);
spinner_->setFixedWidth(40);
spinner_->hide();
errorIcon_ = new QLabel(this);
errorIcon_->setPixmap(QPixmap(":/icons/icons/error.png"));
errorIcon_->hide();
matrixidLayout_ = new QHBoxLayout();
matrixidLayout_->addWidget(matrixid_input_, 0, Qt::AlignVCenter);
QFont font;
error_matrixid_label_ = new QLabel(this);
error_matrixid_label_->setFont(font);
error_matrixid_label_->setWordWrap(true);
password_input_ = new TextField(this);
password_input_->setLabel(tr("Password"));
password_input_->setEchoMode(QLineEdit::Password);
password_input_->setToolTip(tr("Your password."));
deviceName_ = new TextField(this);
deviceName_->setLabel(tr("Device name"));
deviceName_->setToolTip(
tr("A name for this device, which will be shown to others, when verifying your devices. "
"If none is provided a default is used."));
serverInput_ = new TextField(this);
serverInput_->setLabel(tr("Homeserver address"));
serverInput_->setPlaceholderText(tr("server.my:8787"));
serverInput_->setToolTip(tr("The address that can be used to contact you homeservers "
"client API.\nExample: https://server.my:8787"));
serverInput_->hide();
serverLayout_ = new QHBoxLayout();
serverLayout_->addWidget(serverInput_, 0, Qt::AlignVCenter);
form_layout_->addLayout(matrixidLayout_);
form_layout_->addWidget(error_matrixid_label_, 0, Qt::AlignHCenter);
form_layout_->addWidget(password_input_);
form_layout_->addWidget(deviceName_, Qt::AlignHCenter);
form_layout_->addLayout(serverLayout_);
error_matrixid_label_->hide();
button_layout_ = new QHBoxLayout();
button_layout_->setSpacing(20);
button_layout_->setContentsMargins(0, 0, 0, 30);
login_button_ = new RaisedButton(tr("LOGIN"), this);
login_button_->setMinimumSize(150, 65);
login_button_->setFontSize(20);
login_button_->setCornerRadius(3);
sso_login_button_ = new RaisedButton(tr("SSO LOGIN"), this);
sso_login_button_->setMinimumSize(150, 65);
sso_login_button_->setFontSize(20);
sso_login_button_->setCornerRadius(3);
sso_login_button_->setVisible(false);
button_layout_->addStretch(1);
button_layout_->addWidget(login_button_);
button_layout_->addWidget(sso_login_button_);
button_layout_->addStretch(1);
error_label_ = new QLabel(this);
error_label_->setFont(font);
error_label_->setWordWrap(true);
top_layout_->addLayout(top_bar_layout_);
top_layout_->addStretch(1);
top_layout_->addLayout(logo_layout_);
top_layout_->addLayout(form_wrapper_);
top_layout_->addStretch(1);
top_layout_->addLayout(button_layout_);
top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter);
top_layout_->addStretch(1);
setLayout(top_layout_);
connect(this, &LoginPage::versionOkCb, this, &LoginPage::versionOk, Qt::QueuedConnection);
connect(this, &LoginPage::versionErrorCb, this, &LoginPage::versionError, Qt::QueuedConnection);
connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
connect(login_button_, &RaisedButton::clicked, this, [this]() {
onLoginButtonClicked(passwordSupported ? LoginMethod::Password : LoginMethod::SSO);
});
connect(sso_login_button_, &RaisedButton::clicked, this, [this]() {
onLoginButtonClicked(LoginMethod::SSO);
});
connect(this,
&LoginPage::showErrorMessage,
this,
static_cast<void (LoginPage::*)(QLabel *, const QString &)>(&LoginPage::showError),
Qt::QueuedConnection);
connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
connect(deviceName_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
connect(serverInput_, SIGNAL(returnPressed()), login_button_, SLOT(click()));
connect(matrixid_input_, SIGNAL(editingFinished()), this, SLOT(onMatrixIdEntered()));
connect(serverInput_, SIGNAL(editingFinished()), this, SLOT(onServerAddressEntered()));
}
void
LoginPage::showError(const QString &msg)
{
auto rect = QFontMetrics(font()).boundingRect(msg);
int width = rect.width();
int height = rect.height();
error_label_->setFixedHeight((int)qCeil(width / 200.0) * height);
error_label_->setText(msg);
auto rect = QFontMetrics(font()).boundingRect(msg);
int width = rect.width();
int height = rect.height();
error_label_->setFixedHeight((int)qCeil(width / 200.0) * height);
error_label_->setText(msg);
}
void
LoginPage::showError(QLabel *label, const QString &msg)
{
auto rect = QFontMetrics(font()).boundingRect(msg);
int width = rect.width();
int height = rect.height();
label->setFixedHeight((int)qCeil(width / 200.0) * height);
label->setText(msg);
auto rect = QFontMetrics(font()).boundingRect(msg);
int width = rect.width();
int height = rect.height();
label->setFixedHeight((int)qCeil(width / 200.0) * height);
label->setText(msg);
}
void
LoginPage::onMatrixIdEntered()
{
error_label_->setText("");
error_label_->setText("");
User user;
User user;
if (!matrixid_input_->isValid()) {
error_matrixid_label_->show();
showError(error_matrixid_label_,
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
return;
if (!matrixid_input_->isValid()) {
error_matrixid_label_->show();
showError(error_matrixid_label_,
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
return;
} else {
error_matrixid_label_->setText("");
error_matrixid_label_->hide();
}
try {
user = parse<User>(matrixid_input_->text().toStdString());
} catch (const std::exception &) {
showError(error_matrixid_label_,
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
return;
}
QString homeServer = QString::fromStdString(user.hostname());
if (homeServer != inferredServerAddress_) {
serverInput_->hide();
serverLayout_->removeWidget(errorIcon_);
errorIcon_->hide();
if (serverInput_->isVisible()) {
matrixidLayout_->removeWidget(spinner_);
serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
spinner_->start();
} else {
error_matrixid_label_->setText("");
error_matrixid_label_->hide();
serverLayout_->removeWidget(spinner_);
matrixidLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
spinner_->start();
}
try {
user = parse<User>(matrixid_input_->text().toStdString());
} catch (const std::exception &) {
showError(error_matrixid_label_,
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
return;
}
inferredServerAddress_ = homeServer;
serverInput_->setText(homeServer);
QString homeServer = QString::fromStdString(user.hostname());
if (homeServer != inferredServerAddress_) {
serverInput_->hide();
serverLayout_->removeWidget(errorIcon_);
errorIcon_->hide();
if (serverInput_->isVisible()) {
matrixidLayout_->removeWidget(spinner_);
serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
spinner_->start();
} else {
serverLayout_->removeWidget(spinner_);
matrixidLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
spinner_->start();
}
inferredServerAddress_ = homeServer;
serverInput_->setText(homeServer);
http::client()->set_server(user.hostname());
http::client()->verify_certificates(
!UserSettings::instance()->disableCertificateValidation());
http::client()->well_known([this](const mtx::responses::WellKnown &res,
mtx::http::RequestErr err) {
if (err) {
if (err->status_code == 404) {
nhlog::net()->info("Autodiscovery: No .well-known.");
checkHomeserverVersion();
return;
}
if (!err->parse_error.empty()) {
emit versionErrorCb(
tr("Autodiscovery failed. Received malformed response."));
nhlog::net()->error(
"Autodiscovery failed. Received malformed response.");
return;
}
emit versionErrorCb(tr("Autodiscovery failed. Unknown error when "
"requesting .well-known."));
nhlog::net()->error("Autodiscovery failed. Unknown error when "
"requesting .well-known. {} {}",
err->status_code,
err->error_code);
return;
}
nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url +
"'");
http::client()->set_server(res.homeserver.base_url);
checkHomeserverVersion();
});
}
http::client()->set_server(user.hostname());
http::client()->verify_certificates(
!UserSettings::instance()->disableCertificateValidation());
http::client()->well_known(
[this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) {
if (err) {
if (err->status_code == 404) {
nhlog::net()->info("Autodiscovery: No .well-known.");
checkHomeserverVersion();
return;
}
if (!err->parse_error.empty()) {
emit versionErrorCb(tr("Autodiscovery failed. Received malformed response."));
nhlog::net()->error("Autodiscovery failed. Received malformed response.");
return;
}
emit versionErrorCb(tr("Autodiscovery failed. Unknown error when "
"requesting .well-known."));
nhlog::net()->error("Autodiscovery failed. Unknown error when "
"requesting .well-known. {} {}",
err->status_code,
err->error_code);
return;
}
nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'");
http::client()->set_server(res.homeserver.base_url);
checkHomeserverVersion();
});
}
}
void
LoginPage::checkHomeserverVersion()
{
http::client()->versions(
[this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
if (err) {
if (err->status_code == 404) {
emit versionErrorCb(tr("The required endpoints were not found. "
"Possibly not a Matrix server."));
return;
}
if (!err->parse_error.empty()) {
emit versionErrorCb(tr("Received malformed response. Make sure "
"the homeserver domain is valid."));
return;
}
emit versionErrorCb(tr(
"An unknown error occured. Make sure the homeserver domain is valid."));
return;
}
http::client()->versions([this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
if (err) {
if (err->status_code == 404) {
emit versionErrorCb(tr("The required endpoints were not found. "
"Possibly not a Matrix server."));
return;
}
http::client()->get_login(
[this](mtx::responses::LoginFlows flows, mtx::http::RequestErr err) {
if (err || flows.flows.empty())
emit versionOkCb(true, false);
bool ssoSupported_ = false;
bool passwordSupported_ = false;
for (const auto &flow : flows.flows) {
if (flow.type == mtx::user_interactive::auth_types::sso) {
ssoSupported_ = true;
} else if (flow.type ==
mtx::user_interactive::auth_types::password) {
passwordSupported_ = true;
}
}
emit versionOkCb(passwordSupported_, ssoSupported_);
});
if (!err->parse_error.empty()) {
emit versionErrorCb(tr("Received malformed response. Make sure "
"the homeserver domain is valid."));
return;
}
emit versionErrorCb(
tr("An unknown error occured. Make sure the homeserver domain is valid."));
return;
}
http::client()->get_login(
[this](mtx::responses::LoginFlows flows, mtx::http::RequestErr err) {
if (err || flows.flows.empty())
emit versionOkCb(true, false);
bool ssoSupported_ = false;
bool passwordSupported_ = false;
for (const auto &flow : flows.flows) {
if (flow.type == mtx::user_interactive::auth_types::sso) {
ssoSupported_ = true;
} else if (flow.type == mtx::user_interactive::auth_types::password) {
passwordSupported_ = true;
}
}
emit versionOkCb(passwordSupported_, ssoSupported_);
});
});
}
void
LoginPage::onServerAddressEntered()
{
error_label_->setText("");
http::client()->verify_certificates(
!UserSettings::instance()->disableCertificateValidation());
http::client()->set_server(serverInput_->text().toStdString());
checkHomeserverVersion();
serverLayout_->removeWidget(errorIcon_);
errorIcon_->hide();
serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
spinner_->start();
error_label_->setText("");
http::client()->verify_certificates(!UserSettings::instance()->disableCertificateValidation());
http::client()->set_server(serverInput_->text().toStdString());
checkHomeserverVersion();
serverLayout_->removeWidget(errorIcon_);
errorIcon_->hide();
serverLayout_->addWidget(spinner_, 0, Qt::AlignVCenter | Qt::AlignRight);
spinner_->start();
}
void
LoginPage::versionError(const QString &error)
{
showError(error_label_, error);
serverInput_->show();
spinner_->stop();
serverLayout_->removeWidget(spinner_);
serverLayout_->addWidget(errorIcon_, 0, Qt::AlignVCenter | Qt::AlignRight);
errorIcon_->show();
matrixidLayout_->removeWidget(spinner_);
showError(error_label_, error);
serverInput_->show();
spinner_->stop();
serverLayout_->removeWidget(spinner_);
serverLayout_->addWidget(errorIcon_, 0, Qt::AlignVCenter | Qt::AlignRight);
errorIcon_->show();
matrixidLayout_->removeWidget(spinner_);
}
void
LoginPage::versionOk(bool passwordSupported_, bool ssoSupported_)
{
passwordSupported = passwordSupported_;
ssoSupported = ssoSupported_;
passwordSupported = passwordSupported_;
ssoSupported = ssoSupported_;
serverLayout_->removeWidget(spinner_);
matrixidLayout_->removeWidget(spinner_);
spinner_->stop();
serverLayout_->removeWidget(spinner_);
matrixidLayout_->removeWidget(spinner_);
spinner_->stop();
sso_login_button_->setVisible(ssoSupported);
login_button_->setVisible(passwordSupported);
sso_login_button_->setVisible(ssoSupported);
login_button_->setVisible(passwordSupported);
if (serverInput_->isVisible())
serverInput_->hide();
if (serverInput_->isVisible())
serverInput_->hide();
}
void
LoginPage::onLoginButtonClicked(LoginMethod loginMethod)
{
error_label_->setText("");
User user;
error_label_->setText("");
User user;
if (!matrixid_input_->isValid()) {
error_matrixid_label_->show();
showError(error_matrixid_label_,
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
return;
} else {
error_matrixid_label_->setText("");
error_matrixid_label_->hide();
}
try {
user = parse<User>(matrixid_input_->text().toStdString());
} catch (const std::exception &) {
showError(error_matrixid_label_,
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
return;
}
if (loginMethod == LoginMethod::Password) {
if (password_input_->text().isEmpty())
return showError(error_label_, tr("Empty password"));
http::client()->login(
user.localpart(),
password_input_->text().toStdString(),
deviceName_->text().trimmed().isEmpty() ? initialDeviceName()
: deviceName_->text().toStdString(),
[this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
if (err) {
auto error = err->matrix_error.error;
if (error.empty())
error = err->parse_error;
showErrorMessage(error_label_, QString::fromStdString(error));
emit errorOccurred();
return;
}
if (res.well_known) {
http::client()->set_server(res.well_known->homeserver.base_url);
nhlog::net()->info("Login requested to user server: " +
res.well_known->homeserver.base_url);
}
emit loginOk(res);
});
} else {
auto sso = new SSOHandler();
connect(sso, &SSOHandler::ssoSuccess, this, [this, sso](std::string token) {
mtx::requests::Login req{};
req.token = token;
req.type = mtx::user_interactive::auth_types::token;
req.device_id = deviceName_->text().trimmed().isEmpty()
? initialDeviceName()
: deviceName_->text().toStdString();
http::client()->login(
req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
if (err) {
showErrorMessage(error_label_,
QString::fromStdString(err->matrix_error.error));
emit errorOccurred();
return;
}
if (!matrixid_input_->isValid()) {
error_matrixid_label_->show();
showError(error_matrixid_label_,
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
return;
} else {
error_matrixid_label_->setText("");
error_matrixid_label_->hide();
}
if (res.well_known) {
http::client()->set_server(res.well_known->homeserver.base_url);
nhlog::net()->info("Login requested to user server: " +
res.well_known->homeserver.base_url);
}
try {
user = parse<User>(matrixid_input_->text().toStdString());
} catch (const std::exception &) {
showError(error_matrixid_label_,
tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
return;
}
emit loginOk(res);
});
sso->deleteLater();
});
connect(sso, &SSOHandler::ssoFailed, this, [this, sso]() {
showErrorMessage(error_label_, tr("SSO login failed"));
emit errorOccurred();
sso->deleteLater();
});
if (loginMethod == LoginMethod::Password) {
if (password_input_->text().isEmpty())
return showError(error_label_, tr("Empty password"));
http::client()->login(
user.localpart(),
password_input_->text().toStdString(),
deviceName_->text().trimmed().isEmpty() ? initialDeviceName()
: deviceName_->text().toStdString(),
[this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
if (err) {
auto error = err->matrix_error.error;
if (error.empty())
error = err->parse_error;
showErrorMessage(error_label_, QString::fromStdString(error));
emit errorOccurred();
return;
}
if (res.well_known) {
http::client()->set_server(res.well_known->homeserver.base_url);
nhlog::net()->info("Login requested to user server: " +
res.well_known->homeserver.base_url);
}
emit loginOk(res);
});
} else {
auto sso = new SSOHandler();
connect(sso, &SSOHandler::ssoSuccess, this, [this, sso](std::string token) {
mtx::requests::Login req{};
req.token = token;
req.type = mtx::user_interactive::auth_types::token;
req.device_id = deviceName_->text().trimmed().isEmpty()
? initialDeviceName()
: deviceName_->text().toStdString();
http::client()->login(
req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) {
if (err) {
showErrorMessage(
error_label_,
QString::fromStdString(err->matrix_error.error));
emit errorOccurred();
return;
}
if (res.well_known) {
http::client()->set_server(
res.well_known->homeserver.base_url);
nhlog::net()->info("Login requested to user server: " +
res.well_known->homeserver.base_url);
}
emit loginOk(res);
});
sso->deleteLater();
});
connect(sso, &SSOHandler::ssoFailed, this, [this, sso]() {
showErrorMessage(error_label_, tr("SSO login failed"));
emit errorOccurred();
sso->deleteLater();
});
QDesktopServices::openUrl(
QString::fromStdString(http::client()->login_sso_redirect(sso->url())));
}
QDesktopServices::openUrl(
QString::fromStdString(http::client()->login_sso_redirect(sso->url())));
}
emit loggingIn();
emit loggingIn();
}
void
LoginPage::reset()
{
matrixid_input_->clear();
password_input_->clear();
password_input_->show();
serverInput_->clear();
spinner_->stop();
errorIcon_->hide();
serverLayout_->removeWidget(spinner_);
serverLayout_->removeWidget(errorIcon_);
matrixidLayout_->removeWidget(spinner_);
inferredServerAddress_.clear();
matrixid_input_->clear();
password_input_->clear();
password_input_->show();
serverInput_->clear();
spinner_->stop();
errorIcon_->hide();
serverLayout_->removeWidget(spinner_);
serverLayout_->removeWidget(errorIcon_);
matrixidLayout_->removeWidget(spinner_);
inferredServerAddress_.clear();
}
void
LoginPage::onBackButtonClicked()
{
emit backButtonClicked();
emit backButtonClicked();
}
void
LoginPage::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}

@ -24,101 +24,101 @@ struct Login;
class LoginPage : public QWidget
{
Q_OBJECT
Q_OBJECT
public:
enum class LoginMethod
{
Password,
SSO,
};
enum class LoginMethod
{
Password,
SSO,
};
LoginPage(QWidget *parent = nullptr);
LoginPage(QWidget *parent = nullptr);
void reset();
void reset();
signals:
void backButtonClicked();
void loggingIn();
void errorOccurred();
void backButtonClicked();
void loggingIn();
void errorOccurred();
//! Used to trigger the corresponding slot outside of the main thread.
void versionErrorCb(const QString &err);
void versionOkCb(bool passwordSupported, bool ssoSupported);
//! Used to trigger the corresponding slot outside of the main thread.
void versionErrorCb(const QString &err);
void versionOkCb(bool passwordSupported, bool ssoSupported);
void loginOk(const mtx::responses::Login &res);
void showErrorMessage(QLabel *label, const QString &msg);
void loginOk(const mtx::responses::Login &res);
void showErrorMessage(QLabel *label, const QString &msg);
protected:
void paintEvent(QPaintEvent *event) override;
void paintEvent(QPaintEvent *event) override;
public slots:
// Displays errors produced during the login.
void showError(const QString &msg);
void showError(QLabel *label, const QString &msg);
// Displays errors produced during the login.
void showError(const QString &msg);
void showError(QLabel *label, const QString &msg);
private slots:
// Callback for the back button.
void onBackButtonClicked();
// Callback for the back button.
void onBackButtonClicked();
// Callback for the login button.
void onLoginButtonClicked(LoginMethod loginMethod);
// Callback for the login button.
void onLoginButtonClicked(LoginMethod loginMethod);
// Callback for probing the server found in the mxid
void onMatrixIdEntered();
// Callback for probing the server found in the mxid
void onMatrixIdEntered();
// Callback for probing the manually entered server
void onServerAddressEntered();
// Callback for probing the manually entered server
void onServerAddressEntered();
// Callback for errors produced during server probing
void versionError(const QString &error_message);
// Callback for successful server probing
void versionOk(bool passwordSupported, bool ssoSupported);
// Callback for errors produced during server probing
void versionError(const QString &error_message);
// Callback for successful server probing
void versionOk(bool passwordSupported, bool ssoSupported);
private:
void checkHomeserverVersion();
std::string initialDeviceName()
{
void checkHomeserverVersion();
std::string initialDeviceName()
{
#if defined(Q_OS_MAC)
return "Nheko on macOS";
return "Nheko on macOS";
#elif defined(Q_OS_LINUX)
return "Nheko on Linux";
return "Nheko on Linux";
#elif defined(Q_OS_WIN)
return "Nheko on Windows";
return "Nheko on Windows";
#elif defined(Q_OS_FREEBSD)
return "Nheko on FreeBSD";
return "Nheko on FreeBSD";
#else
return "Nheko";
return "Nheko";
#endif
}
}
QVBoxLayout *top_layout_;
QVBoxLayout *top_layout_;
QHBoxLayout *top_bar_layout_;
QHBoxLayout *logo_layout_;
QHBoxLayout *button_layout_;
QHBoxLayout *top_bar_layout_;
QHBoxLayout *logo_layout_;
QHBoxLayout *button_layout_;
QLabel *logo_;
QLabel *error_label_;
QLabel *error_matrixid_label_;
QLabel *logo_;
QLabel *error_label_;
QLabel *error_matrixid_label_;
QHBoxLayout *serverLayout_;
QHBoxLayout *matrixidLayout_;
LoadingIndicator *spinner_;
QLabel *errorIcon_;
QString inferredServerAddress_;
QHBoxLayout *serverLayout_;
QHBoxLayout *matrixidLayout_;
LoadingIndicator *spinner_;
QLabel *errorIcon_;
QString inferredServerAddress_;
FlatButton *back_button_;
RaisedButton *login_button_, *sso_login_button_;
FlatButton *back_button_;
RaisedButton *login_button_, *sso_login_button_;
QWidget *form_widget_;
QHBoxLayout *form_wrapper_;
QVBoxLayout *form_layout_;
QWidget *form_widget_;
QHBoxLayout *form_wrapper_;
QVBoxLayout *form_layout_;
TextField *matrixid_input_;
TextField *password_input_;
TextField *deviceName_;
TextField *serverInput_;
bool passwordSupported = true;
bool ssoSupported = false;
TextField *matrixid_input_;
TextField *password_input_;
TextField *deviceName_;
TextField *serverInput_;
bool passwordSupported = true;
bool ssoSupported = false;
};

@ -43,415 +43,407 @@ MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, userSettings_{UserSettings::instance()}
{
instance_ = this;
setWindowTitle(0);
setObjectName("MainWindow");
modal_ = new OverlayModal(this);
restoreWindowSize();
QFont font;
font.setStyleStrategy(QFont::PreferAntialias);
setFont(font);
trayIcon_ = new TrayIcon(":/logos/nheko.svg", this);
welcome_page_ = new WelcomePage(this);
login_page_ = new LoginPage(this);
register_page_ = new RegisterPage(this);
chat_page_ = new ChatPage(userSettings_, this);
userSettingsPage_ = new UserSettingsPage(userSettings_, this);
// Initialize sliding widget manager.
pageStack_ = new QStackedWidget(this);
pageStack_->addWidget(welcome_page_);
pageStack_->addWidget(login_page_);
pageStack_->addWidget(register_page_);
pageStack_->addWidget(chat_page_);
pageStack_->addWidget(userSettingsPage_);
setCentralWidget(pageStack_);
connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage()));
connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage()));
connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar);
connect(
register_page_, &RegisterPage::registering, this, &MainWindow::showOverlayProgressBar);
connect(
login_page_, &LoginPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); });
connect(register_page_, &RegisterPage::errorOccurred, this, [this]() {
removeOverlayProgressBar();
});
connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
connect(chat_page_, &ChatPage::closing, this, &MainWindow::showWelcomePage);
connect(
chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar);
connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle);
connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int)));
connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) {
login_page_->showError(msg);
showLoginPage();
});
connect(userSettingsPage_, &UserSettingsPage::moveBack, this, [this]() {
pageStack_->setCurrentWidget(chat_page_);
});
connect(
userSettingsPage_, SIGNAL(trayOptionChanged(bool)), trayIcon_, SLOT(setVisible(bool)));
connect(
userSettingsPage_, &UserSettingsPage::themeChanged, chat_page_, &ChatPage::themeChanged);
connect(trayIcon_,
SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
this,
SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar()));
connect(this, &MainWindow::focusChanged, chat_page_, &ChatPage::chatFocusChanged);
connect(
chat_page_, &ChatPage::showUserSettingsPage, this, &MainWindow::showUserSettingsPage);
connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) {
http::client()->set_user(res.user_id);
showChatPage();
});
connect(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage);
QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this);
connect(quitShortcut, &QShortcut::activated, this, QApplication::quit);
trayIcon_->setVisible(userSettings_->tray());
// load cache on event loop
QTimer::singleShot(0, this, [this] {
if (hasActiveUser()) {
QString token = userSettings_->accessToken();
QString home_server = userSettings_->homeserver();
QString user_id = userSettings_->userId();
QString device_id = userSettings_->deviceId();
http::client()->set_access_token(token.toStdString());
http::client()->set_server(home_server.toStdString());
http::client()->set_device_id(device_id.toStdString());
try {
using namespace mtx::identifiers;
http::client()->set_user(parse<User>(user_id.toStdString()));
} catch (const std::invalid_argument &) {
nhlog::ui()->critical("bootstrapped with invalid user_id: {}",
user_id.toStdString());
}
showChatPage();
}
});
instance_ = this;
setWindowTitle(0);
setObjectName("MainWindow");
modal_ = new OverlayModal(this);
restoreWindowSize();
QFont font;
font.setStyleStrategy(QFont::PreferAntialias);
setFont(font);
trayIcon_ = new TrayIcon(":/logos/nheko.svg", this);
welcome_page_ = new WelcomePage(this);
login_page_ = new LoginPage(this);
register_page_ = new RegisterPage(this);
chat_page_ = new ChatPage(userSettings_, this);
userSettingsPage_ = new UserSettingsPage(userSettings_, this);
// Initialize sliding widget manager.
pageStack_ = new QStackedWidget(this);
pageStack_->addWidget(welcome_page_);
pageStack_->addWidget(login_page_);
pageStack_->addWidget(register_page_);
pageStack_->addWidget(chat_page_);
pageStack_->addWidget(userSettingsPage_);
setCentralWidget(pageStack_);
connect(welcome_page_, SIGNAL(userLogin()), this, SLOT(showLoginPage()));
connect(welcome_page_, SIGNAL(userRegister()), this, SLOT(showRegisterPage()));
connect(login_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
connect(login_page_, &LoginPage::loggingIn, this, &MainWindow::showOverlayProgressBar);
connect(register_page_, &RegisterPage::registering, this, &MainWindow::showOverlayProgressBar);
connect(login_page_, &LoginPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); });
connect(
register_page_, &RegisterPage::errorOccurred, this, [this]() { removeOverlayProgressBar(); });
connect(register_page_, SIGNAL(backButtonClicked()), this, SLOT(showWelcomePage()));
connect(chat_page_, &ChatPage::closing, this, &MainWindow::showWelcomePage);
connect(
chat_page_, &ChatPage::showOverlayProgressBar, this, &MainWindow::showOverlayProgressBar);
connect(chat_page_, &ChatPage::unreadMessages, this, &MainWindow::setWindowTitle);
connect(chat_page_, SIGNAL(unreadMessages(int)), trayIcon_, SLOT(setUnreadCount(int)));
connect(chat_page_, &ChatPage::showLoginPage, this, [this](const QString &msg) {
login_page_->showError(msg);
showLoginPage();
});
connect(userSettingsPage_, &UserSettingsPage::moveBack, this, [this]() {
pageStack_->setCurrentWidget(chat_page_);
});
connect(userSettingsPage_, SIGNAL(trayOptionChanged(bool)), trayIcon_, SLOT(setVisible(bool)));
connect(
userSettingsPage_, &UserSettingsPage::themeChanged, chat_page_, &ChatPage::themeChanged);
connect(trayIcon_,
SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
this,
SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar()));
connect(this, &MainWindow::focusChanged, chat_page_, &ChatPage::chatFocusChanged);
connect(chat_page_, &ChatPage::showUserSettingsPage, this, &MainWindow::showUserSettingsPage);
connect(login_page_, &LoginPage::loginOk, this, [this](const mtx::responses::Login &res) {
http::client()->set_user(res.user_id);
showChatPage();
});
connect(register_page_, &RegisterPage::registerOk, this, &MainWindow::showChatPage);
QShortcut *quitShortcut = new QShortcut(QKeySequence::Quit, this);
connect(quitShortcut, &QShortcut::activated, this, QApplication::quit);
trayIcon_->setVisible(userSettings_->tray());
// load cache on event loop
QTimer::singleShot(0, this, [this] {
if (hasActiveUser()) {
QString token = userSettings_->accessToken();
QString home_server = userSettings_->homeserver();
QString user_id = userSettings_->userId();
QString device_id = userSettings_->deviceId();
http::client()->set_access_token(token.toStdString());
http::client()->set_server(home_server.toStdString());
http::client()->set_device_id(device_id.toStdString());
try {
using namespace mtx::identifiers;
http::client()->set_user(parse<User>(user_id.toStdString()));
} catch (const std::invalid_argument &) {
nhlog::ui()->critical("bootstrapped with invalid user_id: {}",
user_id.toStdString());
}
showChatPage();
}
});
}
void
MainWindow::setWindowTitle(int notificationCount)
{
QString name = "nheko";
if (!userSettings_.data()->profile().isEmpty())
name += " | " + userSettings_.data()->profile();
if (notificationCount > 0) {
name.append(QString{" (%1)"}.arg(notificationCount));
}
QMainWindow::setWindowTitle(name);
QString name = "nheko";
if (!userSettings_.data()->profile().isEmpty())
name += " | " + userSettings_.data()->profile();
if (notificationCount > 0) {
name.append(QString{" (%1)"}.arg(notificationCount));
}
QMainWindow::setWindowTitle(name);
}
bool
MainWindow::event(QEvent *event)
{
auto type = event->type();
if (type == QEvent::WindowActivate) {
emit focusChanged(true);
} else if (type == QEvent::WindowDeactivate) {
emit focusChanged(false);
}
return QMainWindow::event(event);
auto type = event->type();
if (type == QEvent::WindowActivate) {
emit focusChanged(true);
} else if (type == QEvent::WindowDeactivate) {
emit focusChanged(false);
}
return QMainWindow::event(event);
}
void
MainWindow::restoreWindowSize()
{
int savedWidth = userSettings_->qsettings()->value("window/width").toInt();
int savedheight = userSettings_->qsettings()->value("window/height").toInt();
int savedWidth = userSettings_->qsettings()->value("window/width").toInt();
int savedheight = userSettings_->qsettings()->value("window/height").toInt();
nhlog::ui()->info("Restoring window size {}x{}", savedWidth, savedheight);
nhlog::ui()->info("Restoring window size {}x{}", savedWidth, savedheight);
if (savedWidth == 0 || savedheight == 0)
resize(conf::window::width, conf::window::height);
else
resize(savedWidth, savedheight);
if (savedWidth == 0 || savedheight == 0)
resize(conf::window::width, conf::window::height);
else
resize(savedWidth, savedheight);
}
void
MainWindow::saveCurrentWindowSize()
{
auto settings = userSettings_->qsettings();
QSize current = size();
auto settings = userSettings_->qsettings();
QSize current = size();
settings->setValue("window/width", current.width());
settings->setValue("window/height", current.height());
settings->setValue("window/width", current.width());
settings->setValue("window/height", current.height());
}
void
MainWindow::removeOverlayProgressBar()
{
QTimer *timer = new QTimer(this);
timer->setSingleShot(true);
QTimer *timer = new QTimer(this);
timer->setSingleShot(true);
connect(timer, &QTimer::timeout, [this, timer]() {
timer->deleteLater();
connect(timer, &QTimer::timeout, [this, timer]() {
timer->deleteLater();
if (modal_)
modal_->hide();
if (modal_)
modal_->hide();
if (spinner_)
spinner_->stop();
});
if (spinner_)
spinner_->stop();
});
// FIXME: Snackbar doesn't work if it's initialized in the constructor.
QTimer::singleShot(0, this, [this]() {
snackBar_ = new SnackBar(this);
connect(chat_page_, &ChatPage::showNotification, snackBar_, &SnackBar::showMessage);
});
// FIXME: Snackbar doesn't work if it's initialized in the constructor.
QTimer::singleShot(0, this, [this]() {
snackBar_ = new SnackBar(this);
connect(chat_page_, &ChatPage::showNotification, snackBar_, &SnackBar::showMessage);
});
timer->start(50);
timer->start(50);
}
void
MainWindow::showChatPage()
{
auto userid = QString::fromStdString(http::client()->user_id().to_string());
auto device_id = QString::fromStdString(http::client()->device_id());
auto homeserver = QString::fromStdString(http::client()->server() + ":" +
std::to_string(http::client()->port()));
auto token = QString::fromStdString(http::client()->access_token());
userSettings_.data()->setUserId(userid);
userSettings_.data()->setAccessToken(token);
userSettings_.data()->setDeviceId(device_id);
userSettings_.data()->setHomeserver(homeserver);
showOverlayProgressBar();
pageStack_->setCurrentWidget(chat_page_);
pageStack_->removeWidget(welcome_page_);
pageStack_->removeWidget(login_page_);
pageStack_->removeWidget(register_page_);
login_page_->reset();
chat_page_->bootstrap(userid, homeserver, token);
connect(cache::client(),
&Cache::secretChanged,
userSettingsPage_,
&UserSettingsPage::updateSecretStatus);
emit reload();
auto userid = QString::fromStdString(http::client()->user_id().to_string());
auto device_id = QString::fromStdString(http::client()->device_id());
auto homeserver = QString::fromStdString(http::client()->server() + ":" +
std::to_string(http::client()->port()));
auto token = QString::fromStdString(http::client()->access_token());
userSettings_.data()->setUserId(userid);
userSettings_.data()->setAccessToken(token);
userSettings_.data()->setDeviceId(device_id);
userSettings_.data()->setHomeserver(homeserver);
showOverlayProgressBar();
pageStack_->setCurrentWidget(chat_page_);
pageStack_->removeWidget(welcome_page_);
pageStack_->removeWidget(login_page_);
pageStack_->removeWidget(register_page_);
login_page_->reset();
chat_page_->bootstrap(userid, homeserver, token);
connect(cache::client(),
&Cache::secretChanged,
userSettingsPage_,
&UserSettingsPage::updateSecretStatus);
emit reload();
}
void
MainWindow::closeEvent(QCloseEvent *event)
{
if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) {
if (QMessageBox::question(this, "nheko", "A call is in progress. Quit?") !=
QMessageBox::Yes) {
event->ignore();
return;
}
if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) {
if (QMessageBox::question(this, "nheko", "A call is in progress. Quit?") !=
QMessageBox::Yes) {
event->ignore();
return;
}
}
if (!qApp->isSavingSession() && isVisible() && pageSupportsTray() &&
userSettings_->tray()) {
event->ignore();
hide();
}
if (!qApp->isSavingSession() && isVisible() && pageSupportsTray() && userSettings_->tray()) {
event->ignore();
hide();
}
}
void
MainWindow::iconActivated(QSystemTrayIcon::ActivationReason reason)
{
switch (reason) {
case QSystemTrayIcon::Trigger:
if (!isVisible()) {
show();
} else {
hide();
}
break;
default:
break;
switch (reason) {
case QSystemTrayIcon::Trigger:
if (!isVisible()) {
show();
} else {
hide();
}
break;
default:
break;
}
}
bool
MainWindow::hasActiveUser()
{
auto settings = userSettings_->qsettings();
QString prefix;
if (userSettings_->profile() != "")
prefix = "profile/" + userSettings_->profile() + "/";
return settings->contains(prefix + "auth/access_token") &&
settings->contains(prefix + "auth/home_server") &&
settings->contains(prefix + "auth/user_id");
auto settings = userSettings_->qsettings();
QString prefix;
if (userSettings_->profile() != "")
prefix = "profile/" + userSettings_->profile() + "/";
return settings->contains(prefix + "auth/access_token") &&
settings->contains(prefix + "auth/home_server") &&
settings->contains(prefix + "auth/user_id");
}
void
MainWindow::openLeaveRoomDialog(const QString &room_id)
{
auto dialog = new dialogs::LeaveRoom(this);
connect(dialog, &dialogs::LeaveRoom::leaving, this, [this, room_id]() {
chat_page_->leaveRoom(room_id);
});
auto dialog = new dialogs::LeaveRoom(this);
connect(dialog, &dialogs::LeaveRoom::leaving, this, [this, room_id]() {
chat_page_->leaveRoom(room_id);
});
showDialog(dialog);
showDialog(dialog);
}
void
MainWindow::showOverlayProgressBar()
{
spinner_ = new LoadingIndicator(this);
spinner_->setFixedHeight(100);
spinner_->setFixedWidth(100);
spinner_->setObjectName("ChatPageLoadSpinner");
spinner_->start();
spinner_ = new LoadingIndicator(this);
spinner_->setFixedHeight(100);
spinner_->setFixedWidth(100);
spinner_->setObjectName("ChatPageLoadSpinner");
spinner_->start();
showSolidOverlayModal(spinner_);
showSolidOverlayModal(spinner_);
}
void
MainWindow::openJoinRoomDialog(std::function<void(const QString &room_id)> callback)
{
auto dialog = new dialogs::JoinRoom(this);
connect(dialog, &dialogs::JoinRoom::joinRoom, this, [callback](const QString &room) {
if (!room.isEmpty())
callback(room);
});
auto dialog = new dialogs::JoinRoom(this);
connect(dialog, &dialogs::JoinRoom::joinRoom, this, [callback](const QString &room) {
if (!room.isEmpty())
callback(room);
});
showDialog(dialog);
showDialog(dialog);
}
void
MainWindow::openCreateRoomDialog(
std::function<void(const mtx::requests::CreateRoom &request)> callback)
{
auto dialog = new dialogs::CreateRoom(this);
connect(dialog,
&dialogs::CreateRoom::createRoom,
this,
[callback](const mtx::requests::CreateRoom &request) { callback(request); });
auto dialog = new dialogs::CreateRoom(this);
connect(dialog,
&dialogs::CreateRoom::createRoom,
this,
[callback](const mtx::requests::CreateRoom &request) { callback(request); });
showDialog(dialog);
showDialog(dialog);
}
void
MainWindow::showTransparentOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags)
{
modal_->setWidget(content);
modal_->setColor(QColor(30, 30, 30, 150));
modal_->setDismissible(true);
modal_->setContentAlignment(flags);
modal_->raise();
modal_->show();
modal_->setWidget(content);
modal_->setColor(QColor(30, 30, 30, 150));
modal_->setDismissible(true);
modal_->setContentAlignment(flags);
modal_->raise();
modal_->show();
}
void
MainWindow::showSolidOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags)
{
modal_->setWidget(content);
modal_->setColor(QColor(30, 30, 30));
modal_->setDismissible(false);
modal_->setContentAlignment(flags);
modal_->raise();
modal_->show();
modal_->setWidget(content);
modal_->setColor(QColor(30, 30, 30));
modal_->setDismissible(false);
modal_->setContentAlignment(flags);
modal_->raise();
modal_->show();
}
void
MainWindow::openLogoutDialog()
{
auto dialog = new dialogs::Logout(this);
connect(dialog, &dialogs::Logout::loggingOut, this, [this]() {
if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) {
if (QMessageBox::question(
this, "nheko", "A call is in progress. Log out?") !=
QMessageBox::Yes) {
return;
}
WebRTCSession::instance().end();
}
chat_page_->initiateLogout();
});
showDialog(dialog);
auto dialog = new dialogs::Logout(this);
connect(dialog, &dialogs::Logout::loggingOut, this, [this]() {
if (WebRTCSession::instance().state() != webrtc::State::DISCONNECTED) {
if (QMessageBox::question(this, "nheko", "A call is in progress. Log out?") !=
QMessageBox::Yes) {
return;
}
WebRTCSession::instance().end();
}
chat_page_->initiateLogout();
});
showDialog(dialog);
}
bool
MainWindow::hasActiveDialogs() const
{
return !modal_ && modal_->isVisible();
return !modal_ && modal_->isVisible();
}
bool
MainWindow::pageSupportsTray() const
{
return !welcome_page_->isVisible() && !login_page_->isVisible() &&
!register_page_->isVisible();
return !welcome_page_->isVisible() && !login_page_->isVisible() && !register_page_->isVisible();
}
void
MainWindow::hideOverlay()
{
if (modal_)
modal_->hide();
if (modal_)
modal_->hide();
}
inline void
MainWindow::showDialog(QWidget *dialog)
{
utils::centerWidget(dialog, this);
dialog->raise();
dialog->show();
utils::centerWidget(dialog, this);
dialog->raise();
dialog->show();
}
void
MainWindow::showWelcomePage()
{
removeOverlayProgressBar();
pageStack_->addWidget(welcome_page_);
pageStack_->setCurrentWidget(welcome_page_);
removeOverlayProgressBar();
pageStack_->addWidget(welcome_page_);
pageStack_->setCurrentWidget(welcome_page_);
}
void
MainWindow::showLoginPage()
{
if (modal_)
modal_->hide();
if (modal_)
modal_->hide();
pageStack_->addWidget(login_page_);
pageStack_->setCurrentWidget(login_page_);
pageStack_->addWidget(login_page_);
pageStack_->setCurrentWidget(login_page_);
}
void
MainWindow::showRegisterPage()
{
pageStack_->addWidget(register_page_);
pageStack_->setCurrentWidget(register_page_);
pageStack_->addWidget(register_page_);
pageStack_->setCurrentWidget(register_page_);
}
void
MainWindow::showUserSettingsPage()
{
pageStack_->setCurrentWidget(userSettingsPage_);
pageStack_->setCurrentWidget(userSettingsPage_);
}

@ -46,93 +46,92 @@ class ReCaptcha;
class MainWindow : public QMainWindow
{
Q_OBJECT
Q_OBJECT
Q_PROPERTY(int x READ x CONSTANT)
Q_PROPERTY(int y READ y CONSTANT)
Q_PROPERTY(int width READ width CONSTANT)
Q_PROPERTY(int height READ height CONSTANT)
Q_PROPERTY(int x READ x CONSTANT)
Q_PROPERTY(int y READ y CONSTANT)
Q_PROPERTY(int width READ width CONSTANT)
Q_PROPERTY(int height READ height CONSTANT)
public:
explicit MainWindow(QWidget *parent = nullptr);
explicit MainWindow(QWidget *parent = nullptr);
static MainWindow *instance() { return instance_; }
void saveCurrentWindowSize();
static MainWindow *instance() { return instance_; }
void saveCurrentWindowSize();
void openLeaveRoomDialog(const QString &room_id);
void openInviteUsersDialog(std::function<void(const QStringList &invitees)> callback);
void openCreateRoomDialog(
std::function<void(const mtx::requests::CreateRoom &request)> callback);
void openJoinRoomDialog(std::function<void(const QString &room_id)> callback);
void openLogoutDialog();
void openLeaveRoomDialog(const QString &room_id);
void openInviteUsersDialog(std::function<void(const QStringList &invitees)> callback);
void openCreateRoomDialog(
std::function<void(const mtx::requests::CreateRoom &request)> callback);
void openJoinRoomDialog(std::function<void(const QString &room_id)> callback);
void openLogoutDialog();
void hideOverlay();
void showSolidOverlayModal(QWidget *content,
QFlags<Qt::AlignmentFlag> flags = Qt::AlignCenter);
void showTransparentOverlayModal(QWidget *content,
QFlags<Qt::AlignmentFlag> flags = Qt::AlignTop |
Qt::AlignHCenter);
void hideOverlay();
void showSolidOverlayModal(QWidget *content, QFlags<Qt::AlignmentFlag> flags = Qt::AlignCenter);
void showTransparentOverlayModal(QWidget *content,
QFlags<Qt::AlignmentFlag> flags = Qt::AlignTop |
Qt::AlignHCenter);
protected:
void closeEvent(QCloseEvent *event) override;
bool event(QEvent *event) override;
void closeEvent(QCloseEvent *event) override;
bool event(QEvent *event) override;
private slots:
//! Handle interaction with the tray icon.
void iconActivated(QSystemTrayIcon::ActivationReason reason);
//! Handle interaction with the tray icon.
void iconActivated(QSystemTrayIcon::ActivationReason reason);
//! Show the welcome page in the main window.
void showWelcomePage();
//! Show the welcome page in the main window.
void showWelcomePage();
//! Show the login page in the main window.
void showLoginPage();
//! Show the login page in the main window.
void showLoginPage();
//! Show the register page in the main window.
void showRegisterPage();
//! Show the register page in the main window.
void showRegisterPage();
//! Show user settings page.
void showUserSettingsPage();
//! Show user settings page.
void showUserSettingsPage();
//! Show the chat page and start communicating with the given access token.
void showChatPage();
//! Show the chat page and start communicating with the given access token.
void showChatPage();
void showOverlayProgressBar();
void removeOverlayProgressBar();
void showOverlayProgressBar();
void removeOverlayProgressBar();
virtual void setWindowTitle(int notificationCount);
virtual void setWindowTitle(int notificationCount);
signals:
void focusChanged(const bool focused);
void reload();
void focusChanged(const bool focused);
void reload();
private:
void showDialog(QWidget *dialog);
bool hasActiveUser();
void restoreWindowSize();
//! Check if there is an open dialog.
bool hasActiveDialogs() const;
//! Check if the current page supports the "minimize to tray" functionality.
bool pageSupportsTray() const;
static MainWindow *instance_;
//! The initial welcome screen.
WelcomePage *welcome_page_;
//! The login screen.
LoginPage *login_page_;
//! The register page.
RegisterPage *register_page_;
//! A stacked widget that handles the transitions between widgets.
QStackedWidget *pageStack_;
//! The main chat area.
ChatPage *chat_page_;
UserSettingsPage *userSettingsPage_;
QSharedPointer<UserSettings> userSettings_;
//! Tray icon that shows the unread message count.
TrayIcon *trayIcon_;
//! Notifications display.
SnackBar *snackBar_ = nullptr;
//! Overlay modal used to project other widgets.
OverlayModal *modal_ = nullptr;
LoadingIndicator *spinner_ = nullptr;
void showDialog(QWidget *dialog);
bool hasActiveUser();
void restoreWindowSize();
//! Check if there is an open dialog.
bool hasActiveDialogs() const;
//! Check if the current page supports the "minimize to tray" functionality.
bool pageSupportsTray() const;
static MainWindow *instance_;
//! The initial welcome screen.
WelcomePage *welcome_page_;
//! The login screen.
LoginPage *login_page_;
//! The register page.
RegisterPage *register_page_;
//! A stacked widget that handles the transitions between widgets.
QStackedWidget *pageStack_;
//! The main chat area.
ChatPage *chat_page_;
UserSettingsPage *userSettingsPage_;
QSharedPointer<UserSettings> userSettings_;
//! Tray icon that shows the unread message count.
TrayIcon *trayIcon_;
//! Notifications display.
SnackBar *snackBar_ = nullptr;
//! Overlay modal used to project other widgets.
OverlayModal *modal_ = nullptr;
LoadingIndicator *spinner_ = nullptr;
};

@ -37,31 +37,31 @@ namespace http {
mtx::http::Client *
client()
{
return client_.get();
return client_.get();
}
bool
is_logged_in()
{
return !client_->access_token().empty();
return !client_->access_token().empty();
}
void
init()
{
qRegisterMetaType<mtx::responses::Login>();
qRegisterMetaType<mtx::responses::Messages>();
qRegisterMetaType<mtx::responses::Notifications>();
qRegisterMetaType<mtx::responses::Rooms>();
qRegisterMetaType<mtx::responses::Sync>();
qRegisterMetaType<mtx::responses::JoinedGroups>();
qRegisterMetaType<mtx::responses::GroupProfile>();
qRegisterMetaType<std::string>();
qRegisterMetaType<nlohmann::json>();
qRegisterMetaType<std::vector<std::string>>();
qRegisterMetaType<std::vector<QString>>();
qRegisterMetaType<std::map<QString, bool>>("std::map<QString, bool>");
qRegisterMetaType<std::set<QString>>();
qRegisterMetaType<mtx::responses::Login>();
qRegisterMetaType<mtx::responses::Messages>();
qRegisterMetaType<mtx::responses::Notifications>();
qRegisterMetaType<mtx::responses::Rooms>();
qRegisterMetaType<mtx::responses::Sync>();
qRegisterMetaType<mtx::responses::JoinedGroups>();
qRegisterMetaType<mtx::responses::GroupProfile>();
qRegisterMetaType<std::string>();
qRegisterMetaType<nlohmann::json>();
qRegisterMetaType<std::vector<std::string>>();
qRegisterMetaType<std::vector<QString>>();
qRegisterMetaType<std::map<QString, bool>>("std::map<QString, bool>");
qRegisterMetaType<std::set<QString>>();
}
} // namespace http

@ -15,98 +15,96 @@ MemberList::MemberList(const QString &room_id, QObject *parent)
: QAbstractListModel{parent}
, room_id_{room_id}
{
try {
info_ = cache::singleRoomInfo(room_id_.toStdString());
} catch (const lmdb::error &) {
nhlog::db()->warn("failed to retrieve room info from cache: {}",
room_id_.toStdString());
}
try {
info_ = cache::singleRoomInfo(room_id_.toStdString());
} catch (const lmdb::error &) {
nhlog::db()->warn("failed to retrieve room info from cache: {}", room_id_.toStdString());
}
try {
auto members = cache::getMembers(room_id_.toStdString());
addUsers(members);
numUsersLoaded_ = members.size();
} catch (const lmdb::error &e) {
nhlog::db()->critical("Failed to retrieve members from cache: {}", e.what());
}
try {
auto members = cache::getMembers(room_id_.toStdString());
addUsers(members);
numUsersLoaded_ = members.size();
} catch (const lmdb::error &e) {
nhlog::db()->critical("Failed to retrieve members from cache: {}", e.what());
}
}
void
MemberList::addUsers(const std::vector<RoomMember> &members)
{
beginInsertRows(
QModelIndex{}, m_memberList.count(), m_memberList.count() + members.size() - 1);
beginInsertRows(QModelIndex{}, m_memberList.count(), m_memberList.count() + members.size() - 1);
for (const auto &member : members)
m_memberList.push_back(
{member,
ChatPage::instance()->timelineManager()->rooms()->currentRoom()->avatarUrl(
member.user_id)});
for (const auto &member : members)
m_memberList.push_back(
{member,
ChatPage::instance()->timelineManager()->rooms()->currentRoom()->avatarUrl(
member.user_id)});
endInsertRows();
endInsertRows();
}
QHash<int, QByteArray>
MemberList::roleNames() const
{
return {
{Mxid, "mxid"},
{DisplayName, "displayName"},
{AvatarUrl, "avatarUrl"},
{Trustlevel, "trustlevel"},
};
return {
{Mxid, "mxid"},
{DisplayName, "displayName"},
{AvatarUrl, "avatarUrl"},
{Trustlevel, "trustlevel"},
};
}
QVariant
MemberList::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() >= (int)m_memberList.size() || index.row() < 0)
return {};
if (!index.isValid() || index.row() >= (int)m_memberList.size() || index.row() < 0)
return {};
switch (role) {
case Mxid:
return m_memberList[index.row()].first.user_id;
case DisplayName:
return m_memberList[index.row()].first.display_name;
case AvatarUrl:
return m_memberList[index.row()].second;
case Trustlevel: {
auto stat =
cache::verificationStatus(m_memberList[index.row()].first.user_id.toStdString());
switch (role) {
case Mxid:
return m_memberList[index.row()].first.user_id;
case DisplayName:
return m_memberList[index.row()].first.display_name;
case AvatarUrl:
return m_memberList[index.row()].second;
case Trustlevel: {
auto stat =
cache::verificationStatus(m_memberList[index.row()].first.user_id.toStdString());
if (!stat)
return crypto::Unverified;
if (stat->unverified_device_count)
return crypto::Unverified;
else
return stat->user_verified;
}
default:
return {};
}
if (!stat)
return crypto::Unverified;
if (stat->unverified_device_count)
return crypto::Unverified;
else
return stat->user_verified;
}
default:
return {};
}
}
bool
MemberList::canFetchMore(const QModelIndex &) const
{
const size_t numMembers = rowCount();
if (numMembers > 1 && numMembers < info_.member_count)
return true;
else
return false;
const size_t numMembers = rowCount();
if (numMembers > 1 && numMembers < info_.member_count)
return true;
else
return false;
}
void
MemberList::fetchMore(const QModelIndex &)
{
loadingMoreMembers_ = true;
emit loadingMoreMembersChanged();
loadingMoreMembers_ = true;
emit loadingMoreMembersChanged();
auto members = cache::getMembers(room_id_.toStdString(), rowCount());
addUsers(members);
numUsersLoaded_ += members.size();
emit numUsersLoadedChanged();
auto members = cache::getMembers(room_id_.toStdString(), rowCount());
addUsers(members);
numUsersLoaded_ += members.size();
emit numUsersLoadedChanged();
loadingMoreMembers_ = false;
emit loadingMoreMembersChanged();
loadingMoreMembers_ = false;
emit loadingMoreMembersChanged();
}

@ -10,59 +10,59 @@
class MemberList : public QAbstractListModel
{
Q_OBJECT
Q_OBJECT
Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
Q_PROPERTY(int memberCount READ memberCount NOTIFY memberCountChanged)
Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged)
Q_PROPERTY(QString roomId READ roomId NOTIFY roomIdChanged)
Q_PROPERTY(int numUsersLoaded READ numUsersLoaded NOTIFY numUsersLoadedChanged)
Q_PROPERTY(bool loadingMoreMembers READ loadingMoreMembers NOTIFY loadingMoreMembersChanged)
Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
Q_PROPERTY(int memberCount READ memberCount NOTIFY memberCountChanged)
Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged)
Q_PROPERTY(QString roomId READ roomId NOTIFY roomIdChanged)
Q_PROPERTY(int numUsersLoaded READ numUsersLoaded NOTIFY numUsersLoadedChanged)
Q_PROPERTY(bool loadingMoreMembers READ loadingMoreMembers NOTIFY loadingMoreMembersChanged)
public:
enum Roles
{
Mxid,
DisplayName,
AvatarUrl,
Trustlevel,
};
MemberList(const QString &room_id, QObject *parent = nullptr);
enum Roles
{
Mxid,
DisplayName,
AvatarUrl,
Trustlevel,
};
MemberList(const QString &room_id, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
Q_UNUSED(parent)
return static_cast<int>(m_memberList.size());
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
Q_UNUSED(parent)
return static_cast<int>(m_memberList.size());
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QString roomName() const { return QString::fromStdString(info_.name); }
int memberCount() const { return info_.member_count; }
QString avatarUrl() const { return QString::fromStdString(info_.avatar_url); }
QString roomId() const { return room_id_; }
int numUsersLoaded() const { return numUsersLoaded_; }
bool loadingMoreMembers() const { return loadingMoreMembers_; }
QString roomName() const { return QString::fromStdString(info_.name); }
int memberCount() const { return info_.member_count; }
QString avatarUrl() const { return QString::fromStdString(info_.avatar_url); }
QString roomId() const { return room_id_; }
int numUsersLoaded() const { return numUsersLoaded_; }
bool loadingMoreMembers() const { return loadingMoreMembers_; }
signals:
void roomNameChanged();
void memberCountChanged();
void avatarUrlChanged();
void roomIdChanged();
void numUsersLoadedChanged();
void loadingMoreMembersChanged();
void roomNameChanged();
void memberCountChanged();
void avatarUrlChanged();
void roomIdChanged();
void numUsersLoadedChanged();
void loadingMoreMembersChanged();
public slots:
void addUsers(const std::vector<RoomMember> &users);
void addUsers(const std::vector<RoomMember> &users);
protected:
bool canFetchMore(const QModelIndex &) const override;
void fetchMore(const QModelIndex &) override;
bool canFetchMore(const QModelIndex &) const override;
void fetchMore(const QModelIndex &) override;
private:
QVector<QPair<RoomMember, QString>> m_memberList;
QString room_id_;
RoomInfo info_;
int numUsersLoaded_{0};
bool loadingMoreMembers_{false};
QVector<QPair<RoomMember, QString>> m_memberList;
QString room_id_;
RoomInfo info_;
int numUsersLoaded_{0};
bool loadingMoreMembers_{false};
};

@ -24,70 +24,70 @@ QHash<QString, mtx::crypto::EncryptedFile> infos;
QQuickImageResponse *
MxcImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
{
auto id_ = id;
bool crop = true;
double radius = 0;
auto queryStart = id.lastIndexOf('?');
if (queryStart != -1) {
id_ = id.left(queryStart);
auto query = id.midRef(queryStart + 1);
auto queryBits = query.split('&');
for (auto b : queryBits) {
if (b == "scale") {
crop = false;
} else if (b.startsWith("radius=")) {
radius = b.mid(7).toDouble();
}
}
auto id_ = id;
bool crop = true;
double radius = 0;
auto queryStart = id.lastIndexOf('?');
if (queryStart != -1) {
id_ = id.left(queryStart);
auto query = id.midRef(queryStart + 1);
auto queryBits = query.split('&');
for (auto b : queryBits) {
if (b == "scale") {
crop = false;
} else if (b.startsWith("radius=")) {
radius = b.mid(7).toDouble();
}
}
}
MxcImageResponse *response = new MxcImageResponse(id_, crop, radius, requestedSize);
pool.start(response);
return response;
MxcImageResponse *response = new MxcImageResponse(id_, crop, radius, requestedSize);
pool.start(response);
return response;
}
void
MxcImageProvider::addEncryptionInfo(mtx::crypto::EncryptedFile info)
{
infos.insert(QString::fromStdString(info.url), info);
infos.insert(QString::fromStdString(info.url), info);
}
void
MxcImageResponse::run()
{
MxcImageProvider::download(
m_id,
m_requestedSize,
[this](QString, QSize, QImage image, QString) {
if (image.isNull()) {
m_error = "Failed to download image.";
} else {
m_image = image;
}
emit finished();
},
m_crop,
m_radius);
MxcImageProvider::download(
m_id,
m_requestedSize,
[this](QString, QSize, QImage image, QString) {
if (image.isNull()) {
m_error = "Failed to download image.";
} else {
m_image = image;
}
emit finished();
},
m_crop,
m_radius);
}
static QImage
clipRadius(QImage img, double radius)
{
QImage out(img.size(), QImage::Format_ARGB32_Premultiplied);
out.fill(Qt::transparent);
QImage out(img.size(), QImage::Format_ARGB32_Premultiplied);
out.fill(Qt::transparent);
QPainter painter(&out);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
QPainter painter(&out);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
QPainterPath ppath;
ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize);
QPainterPath ppath;
ppath.addRoundedRect(img.rect(), radius, radius, Qt::SizeMode::RelativeSize);
painter.setClipPath(ppath);
painter.drawImage(img.rect(), img);
painter.setClipPath(ppath);
painter.drawImage(img.rect(), img);
return out;
return out;
}
void
@ -97,187 +97,165 @@ MxcImageProvider::download(const QString &id,
bool crop,
double radius)
{
std::optional<mtx::crypto::EncryptedFile> encryptionInfo;
auto temp = infos.find("mxc://" + id);
if (temp != infos.end())
encryptionInfo = *temp;
if (requestedSize.isValid() && !encryptionInfo) {
QString fileName =
QString("%1_%2x%3_%4_radius%5")
.arg(QString::fromUtf8(id.toUtf8().toBase64(QByteArray::Base64UrlEncoding |
QByteArray::OmitTrailingEquals)))
.arg(requestedSize.width())
.arg(requestedSize.height())
.arg(crop ? "crop" : "scale")
.arg(radius);
QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
"/media_cache",
fileName);
QDir().mkpath(fileInfo.absolutePath());
if (fileInfo.exists()) {
QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath());
if (!image.isNull()) {
image = image.scaled(
requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
if (radius != 0) {
image = clipRadius(std::move(image), radius);
}
if (!image.isNull()) {
then(id, requestedSize, image, fileInfo.absoluteFilePath());
return;
}
}
std::optional<mtx::crypto::EncryptedFile> encryptionInfo;
auto temp = infos.find("mxc://" + id);
if (temp != infos.end())
encryptionInfo = *temp;
if (requestedSize.isValid() && !encryptionInfo) {
QString fileName = QString("%1_%2x%3_%4_radius%5")
.arg(QString::fromUtf8(id.toUtf8().toBase64(
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)))
.arg(requestedSize.width())
.arg(requestedSize.height())
.arg(crop ? "crop" : "scale")
.arg(radius);
QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
"/media_cache",
fileName);
QDir().mkpath(fileInfo.absolutePath());
if (fileInfo.exists()) {
QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath());
if (!image.isNull()) {
image = image.scaled(requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
if (radius != 0) {
image = clipRadius(std::move(image), radius);
}
mtx::http::ThumbOpts opts;
opts.mxc_url = "mxc://" + id.toStdString();
opts.width = requestedSize.width() > 0 ? requestedSize.width() : -1;
opts.height = requestedSize.height() > 0 ? requestedSize.height() : -1;
opts.method = crop ? "crop" : "scale";
http::client()->get_thumbnail(
opts,
[fileInfo, requestedSize, radius, then, id](const std::string &res,
mtx::http::RequestErr err) {
if (err || res.empty()) {
then(id, QSize(), {}, "");
return;
}
auto data = QByteArray(res.data(), (int)res.size());
QImage image = utils::readImage(data);
if (!image.isNull()) {
image = image.scaled(
requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
if (radius != 0) {
image = clipRadius(std::move(image), radius);
}
}
image.setText("mxc url", "mxc://" + id);
if (image.save(fileInfo.absoluteFilePath(), "png"))
nhlog::ui()->debug("Wrote: {}",
fileInfo.absoluteFilePath().toStdString());
else
nhlog::ui()->debug("Failed to write: {}",
fileInfo.absoluteFilePath().toStdString());
then(id, requestedSize, image, fileInfo.absoluteFilePath());
});
} else {
try {
QString fileName =
QString("%1_radius%2")
.arg(QString::fromUtf8(id.toUtf8().toBase64(
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)))
.arg(radius);
QFileInfo fileInfo(
QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
"/media_cache",
fileName);
QDir().mkpath(fileInfo.absolutePath());
if (fileInfo.exists()) {
if (encryptionInfo) {
QFile f(fileInfo.absoluteFilePath());
f.open(QIODevice::ReadOnly);
QByteArray fileData = f.readAll();
auto tempData =
mtx::crypto::to_string(mtx::crypto::decrypt_file(
fileData.toStdString(), encryptionInfo.value()));
auto data =
QByteArray(tempData.data(), (int)tempData.size());
QImage image = utils::readImage(data);
image.setText("mxc url", "mxc://" + id);
if (!image.isNull()) {
if (radius != 0) {
image =
clipRadius(std::move(image), radius);
}
then(id,
requestedSize,
image,
fileInfo.absoluteFilePath());
return;
}
} else {
QImage image =
utils::readImageFromFile(fileInfo.absoluteFilePath());
if (!image.isNull()) {
if (radius != 0) {
image =
clipRadius(std::move(image), radius);
}
then(id,
requestedSize,
image,
fileInfo.absoluteFilePath());
return;
}
}
if (!image.isNull()) {
then(id, requestedSize, image, fileInfo.absoluteFilePath());
return;
}
}
}
mtx::http::ThumbOpts opts;
opts.mxc_url = "mxc://" + id.toStdString();
opts.width = requestedSize.width() > 0 ? requestedSize.width() : -1;
opts.height = requestedSize.height() > 0 ? requestedSize.height() : -1;
opts.method = crop ? "crop" : "scale";
http::client()->get_thumbnail(
opts,
[fileInfo, requestedSize, radius, then, id](const std::string &res,
mtx::http::RequestErr err) {
if (err || res.empty()) {
then(id, QSize(), {}, "");
return;
}
auto data = QByteArray(res.data(), (int)res.size());
QImage image = utils::readImage(data);
if (!image.isNull()) {
image =
image.scaled(requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
if (radius != 0) {
image = clipRadius(std::move(image), radius);
}
}
image.setText("mxc url", "mxc://" + id);
if (image.save(fileInfo.absoluteFilePath(), "png"))
nhlog::ui()->debug("Wrote: {}", fileInfo.absoluteFilePath().toStdString());
else
nhlog::ui()->debug("Failed to write: {}",
fileInfo.absoluteFilePath().toStdString());
then(id, requestedSize, image, fileInfo.absoluteFilePath());
});
} else {
try {
QString fileName = QString("%1_radius%2")
.arg(QString::fromUtf8(id.toUtf8().toBase64(
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)))
.arg(radius);
QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
"/media_cache",
fileName);
QDir().mkpath(fileInfo.absolutePath());
if (fileInfo.exists()) {
if (encryptionInfo) {
QFile f(fileInfo.absoluteFilePath());
f.open(QIODevice::ReadOnly);
QByteArray fileData = f.readAll();
auto tempData = mtx::crypto::to_string(
mtx::crypto::decrypt_file(fileData.toStdString(), encryptionInfo.value()));
auto data = QByteArray(tempData.data(), (int)tempData.size());
QImage image = utils::readImage(data);
image.setText("mxc url", "mxc://" + id);
if (!image.isNull()) {
if (radius != 0) {
image = clipRadius(std::move(image), radius);
}
http::client()->download(
"mxc://" + id.toStdString(),
[fileInfo, requestedSize, then, id, radius, encryptionInfo](
const std::string &res,
const std::string &,
const std::string &originalFilename,
mtx::http::RequestErr err) {
if (err) {
then(id, QSize(), {}, "");
return;
}
auto tempData = res;
QFile f(fileInfo.absoluteFilePath());
if (!f.open(QIODevice::Truncate | QIODevice::WriteOnly)) {
then(id, QSize(), {}, "");
return;
}
f.write(tempData.data(), tempData.size());
f.close();
if (encryptionInfo) {
tempData =
mtx::crypto::to_string(mtx::crypto::decrypt_file(
tempData, encryptionInfo.value()));
auto data =
QByteArray(tempData.data(), (int)tempData.size());
QImage image = utils::readImage(data);
if (radius != 0) {
image = clipRadius(std::move(image), radius);
}
image.setText("original filename",
QString::fromStdString(originalFilename));
image.setText("mxc url", "mxc://" + id);
then(
id, requestedSize, image, fileInfo.absoluteFilePath());
return;
}
QImage image =
utils::readImageFromFile(fileInfo.absoluteFilePath());
if (radius != 0) {
image = clipRadius(std::move(image), radius);
}
image.setText("original filename",
QString::fromStdString(originalFilename));
image.setText("mxc url", "mxc://" + id);
then(id, requestedSize, image, fileInfo.absoluteFilePath());
});
} catch (std::exception &e) {
nhlog::net()->error("Exception while downloading media: {}", e.what());
then(id, requestedSize, image, fileInfo.absoluteFilePath());
return;
}
} else {
QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath());
if (!image.isNull()) {
if (radius != 0) {
image = clipRadius(std::move(image), radius);
}
then(id, requestedSize, image, fileInfo.absoluteFilePath());
return;
}
}
}
http::client()->download(
"mxc://" + id.toStdString(),
[fileInfo, requestedSize, then, id, radius, encryptionInfo](
const std::string &res,
const std::string &,
const std::string &originalFilename,
mtx::http::RequestErr err) {
if (err) {
then(id, QSize(), {}, "");
return;
}
auto tempData = res;
QFile f(fileInfo.absoluteFilePath());
if (!f.open(QIODevice::Truncate | QIODevice::WriteOnly)) {
then(id, QSize(), {}, "");
return;
}
f.write(tempData.data(), tempData.size());
f.close();
if (encryptionInfo) {
tempData = mtx::crypto::to_string(
mtx::crypto::decrypt_file(tempData, encryptionInfo.value()));
auto data = QByteArray(tempData.data(), (int)tempData.size());
QImage image = utils::readImage(data);
if (radius != 0) {
image = clipRadius(std::move(image), radius);
}
image.setText("original filename", QString::fromStdString(originalFilename));
image.setText("mxc url", "mxc://" + id);
then(id, requestedSize, image, fileInfo.absoluteFilePath());
return;
}
QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath());
if (radius != 0) {
image = clipRadius(std::move(image), radius);
}
image.setText("original filename", QString::fromStdString(originalFilename));
image.setText("mxc url", "mxc://" + id);
then(id, requestedSize, image, fileInfo.absoluteFilePath());
});
} catch (std::exception &e) {
nhlog::net()->error("Exception while downloading media: {}", e.what());
}
}
}

@ -19,46 +19,46 @@ class MxcImageResponse
, public QRunnable
{
public:
MxcImageResponse(const QString &id, bool crop, double radius, const QSize &requestedSize)
: m_id(id)
, m_requestedSize(requestedSize)
, m_crop(crop)
, m_radius(radius)
{
setAutoDelete(false);
}
MxcImageResponse(const QString &id, bool crop, double radius, const QSize &requestedSize)
: m_id(id)
, m_requestedSize(requestedSize)
, m_crop(crop)
, m_radius(radius)
{
setAutoDelete(false);
}
QQuickTextureFactory *textureFactory() const override
{
return QQuickTextureFactory::textureFactoryForImage(m_image);
}
QString errorString() const override { return m_error; }
QQuickTextureFactory *textureFactory() const override
{
return QQuickTextureFactory::textureFactoryForImage(m_image);
}
QString errorString() const override { return m_error; }
void run() override;
void run() override;
QString m_id, m_error;
QSize m_requestedSize;
QImage m_image;
bool m_crop;
double m_radius;
QString m_id, m_error;
QSize m_requestedSize;
QImage m_image;
bool m_crop;
double m_radius;
};
class MxcImageProvider
: public QObject
, public QQuickAsyncImageProvider
{
Q_OBJECT
Q_OBJECT
public slots:
QQuickImageResponse *requestImageResponse(const QString &id,
const QSize &requestedSize) override;
QQuickImageResponse *requestImageResponse(const QString &id,
const QSize &requestedSize) override;
static void addEncryptionInfo(mtx::crypto::EncryptedFile info);
static void download(const QString &id,
const QSize &requestedSize,
std::function<void(QString, QSize, QImage, QString)> then,
bool crop = true,
double radius = 0);
static void addEncryptionInfo(mtx::crypto::EncryptedFile info);
static void download(const QString &id,
const QSize &requestedSize,
std::function<void(QString, QSize, QImage, QString)> then,
bool crop = true,
double radius = 0);
private:
QThreadPool pool;
QThreadPool pool;
};

File diff suppressed because it is too large Load Diff

@ -18,32 +18,32 @@ Q_NAMESPACE
enum DecryptionErrorCode
{
NoError,
MissingSession, // Session was not found, retrieve from backup or request from other devices
// and try again
MissingSessionIndex, // Session was found, but it does not reach back enough to this index,
// retrieve from backup or request from other devices and try again
DbError, // DB read failed
DecryptionFailed, // libolm error
ParsingFailed, // Failed to parse the actual event
ReplayAttack, // Megolm index reused
NoError,
MissingSession, // Session was not found, retrieve from backup or request from other devices
// and try again
MissingSessionIndex, // Session was found, but it does not reach back enough to this index,
// retrieve from backup or request from other devices and try again
DbError, // DB read failed
DecryptionFailed, // libolm error
ParsingFailed, // Failed to parse the actual event
ReplayAttack, // Megolm index reused
};
Q_ENUM_NS(DecryptionErrorCode)
struct DecryptionResult
{
DecryptionErrorCode error;
std::optional<std::string> error_message;
std::optional<mtx::events::collections::TimelineEvents> event;
DecryptionErrorCode error;
std::optional<std::string> error_message;
std::optional<mtx::events::collections::TimelineEvents> event;
};
struct OlmMessage
{
std::string sender_key;
std::string sender;
std::string sender_key;
std::string sender;
using RecipientKey = std::string;
std::map<RecipientKey, mtx::events::msg::OlmCipherContent> ciphertext;
using RecipientKey = std::string;
std::map<RecipientKey, mtx::events::msg::OlmCipherContent> ciphertext;
};
void

@ -16,116 +16,115 @@ ReadReceiptsModel::ReadReceiptsModel(QString event_id, QString room_id, QObject
, event_id_{event_id}
, room_id_{room_id}
{
try {
addUsers(cache::readReceipts(event_id_, room_id_));
} catch (const lmdb::error &) {
nhlog::db()->warn("failed to retrieve read receipts for {} {}",
event_id_.toStdString(),
room_id_.toStdString());
return;
}
try {
addUsers(cache::readReceipts(event_id_, room_id_));
} catch (const lmdb::error &) {
nhlog::db()->warn("failed to retrieve read receipts for {} {}",
event_id_.toStdString(),
room_id_.toStdString());
return;
}
connect(cache::client(), &Cache::newReadReceipts, this, &ReadReceiptsModel::update);
connect(cache::client(), &Cache::newReadReceipts, this, &ReadReceiptsModel::update);
}
void
ReadReceiptsModel::update()
{
try {
addUsers(cache::readReceipts(event_id_, room_id_));
} catch (const lmdb::error &) {
nhlog::db()->warn("failed to retrieve read receipts for {} {}",
event_id_.toStdString(),
room_id_.toStdString());
return;
}
try {
addUsers(cache::readReceipts(event_id_, room_id_));
} catch (const lmdb::error &) {
nhlog::db()->warn("failed to retrieve read receipts for {} {}",
event_id_.toStdString(),
room_id_.toStdString());
return;
}
}
QHash<int, QByteArray>
ReadReceiptsModel::roleNames() const
{
// Note: RawTimestamp is purposely not included here
return {
{Mxid, "mxid"},
{DisplayName, "displayName"},
{AvatarUrl, "avatarUrl"},
{Timestamp, "timestamp"},
};
// Note: RawTimestamp is purposely not included here
return {
{Mxid, "mxid"},
{DisplayName, "displayName"},
{AvatarUrl, "avatarUrl"},
{Timestamp, "timestamp"},
};
}
QVariant
ReadReceiptsModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() >= (int)readReceipts_.size() || index.row() < 0)
return {};
switch (role) {
case Mxid:
return readReceipts_[index.row()].first;
case DisplayName:
return cache::displayName(room_id_, readReceipts_[index.row()].first);
case AvatarUrl:
return cache::avatarUrl(room_id_, readReceipts_[index.row()].first);
case Timestamp:
return dateFormat(readReceipts_[index.row()].second);
case RawTimestamp:
return readReceipts_[index.row()].second;
default:
return {};
}
if (!index.isValid() || index.row() >= (int)readReceipts_.size() || index.row() < 0)
return {};
switch (role) {
case Mxid:
return readReceipts_[index.row()].first;
case DisplayName:
return cache::displayName(room_id_, readReceipts_[index.row()].first);
case AvatarUrl:
return cache::avatarUrl(room_id_, readReceipts_[index.row()].first);
case Timestamp:
return dateFormat(readReceipts_[index.row()].second);
case RawTimestamp:
return readReceipts_[index.row()].second;
default:
return {};
}
}
void
ReadReceiptsModel::addUsers(
const std::multimap<uint64_t, std::string, std::greater<uint64_t>> &users)
{
auto newReceipts = users.size() - readReceipts_.size();
if (newReceipts > 0) {
beginInsertRows(
QModelIndex{}, readReceipts_.size(), readReceipts_.size() + newReceipts - 1);
auto newReceipts = users.size() - readReceipts_.size();
for (const auto &user : users) {
QPair<QString, QDateTime> item = {
QString::fromStdString(user.second),
QDateTime::fromMSecsSinceEpoch(user.first)};
if (!readReceipts_.contains(item))
readReceipts_.push_back(item);
}
if (newReceipts > 0) {
beginInsertRows(
QModelIndex{}, readReceipts_.size(), readReceipts_.size() + newReceipts - 1);
endInsertRows();
for (const auto &user : users) {
QPair<QString, QDateTime> item = {QString::fromStdString(user.second),
QDateTime::fromMSecsSinceEpoch(user.first)};
if (!readReceipts_.contains(item))
readReceipts_.push_back(item);
}
endInsertRows();
}
}
QString
ReadReceiptsModel::dateFormat(const QDateTime &then) const
{
auto now = QDateTime::currentDateTime();
auto days = then.daysTo(now);
if (days == 0)
return QLocale::system().toString(then.time(), QLocale::ShortFormat);
else if (days < 2)
return tr("Yesterday, %1")
.arg(QLocale::system().toString(then.time(), QLocale::ShortFormat));
else if (days < 7)
//: %1 is the name of the current day, %2 is the time the read receipt was read. The
//: result may look like this: Monday, 7:15
return QString("%1, %2")
.arg(then.toString("dddd"))
.arg(QLocale::system().toString(then.time(), QLocale::ShortFormat));
auto now = QDateTime::currentDateTime();
auto days = then.daysTo(now);
if (days == 0)
return QLocale::system().toString(then.time(), QLocale::ShortFormat);
else if (days < 2)
return tr("Yesterday, %1")
.arg(QLocale::system().toString(then.time(), QLocale::ShortFormat));
else if (days < 7)
//: %1 is the name of the current day, %2 is the time the read receipt was read. The
//: result may look like this: Monday, 7:15
return QString("%1, %2")
.arg(then.toString("dddd"))
.arg(QLocale::system().toString(then.time(), QLocale::ShortFormat));
return QLocale::system().toString(then.time(), QLocale::ShortFormat);
}
ReadReceiptsProxy::ReadReceiptsProxy(QString event_id, QString room_id, QObject *parent)
: QSortFilterProxyModel{parent}
, model_{event_id, room_id, this}
{
setSourceModel(&model_);
setSortRole(ReadReceiptsModel::RawTimestamp);
sort(0, Qt::DescendingOrder);
setDynamicSortFilter(true);
setSourceModel(&model_);
setSortRole(ReadReceiptsModel::RawTimestamp);
sort(0, Qt::DescendingOrder);
setDynamicSortFilter(true);
}

@ -13,61 +13,61 @@
class ReadReceiptsModel : public QAbstractListModel
{
Q_OBJECT
Q_OBJECT
public:
enum Roles
{
Mxid,
DisplayName,
AvatarUrl,
Timestamp,
RawTimestamp,
};
explicit ReadReceiptsModel(QString event_id, QString room_id, QObject *parent = nullptr);
QString eventId() const { return event_id_; }
QString roomId() const { return room_id_; }
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent) const override
{
Q_UNUSED(parent)
return readReceipts_.size();
}
QVariant data(const QModelIndex &index, int role) const override;
enum Roles
{
Mxid,
DisplayName,
AvatarUrl,
Timestamp,
RawTimestamp,
};
explicit ReadReceiptsModel(QString event_id, QString room_id, QObject *parent = nullptr);
QString eventId() const { return event_id_; }
QString roomId() const { return room_id_; }
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent) const override
{
Q_UNUSED(parent)
return readReceipts_.size();
}
QVariant data(const QModelIndex &index, int role) const override;
public slots:
void addUsers(const std::multimap<uint64_t, std::string, std::greater<uint64_t>> &users);
void update();
void addUsers(const std::multimap<uint64_t, std::string, std::greater<uint64_t>> &users);
void update();
private:
QString dateFormat(const QDateTime &then) const;
QString dateFormat(const QDateTime &then) const;
QString event_id_;
QString room_id_;
QVector<QPair<QString, QDateTime>> readReceipts_;
QString event_id_;
QString room_id_;
QVector<QPair<QString, QDateTime>> readReceipts_;
};
class ReadReceiptsProxy : public QSortFilterProxyModel
{
Q_OBJECT
Q_OBJECT
Q_PROPERTY(QString eventId READ eventId CONSTANT)
Q_PROPERTY(QString roomId READ roomId CONSTANT)
Q_PROPERTY(QString eventId READ eventId CONSTANT)
Q_PROPERTY(QString roomId READ roomId CONSTANT)
public:
explicit ReadReceiptsProxy(QString event_id, QString room_id, QObject *parent = nullptr);
explicit ReadReceiptsProxy(QString event_id, QString room_id, QObject *parent = nullptr);
QString eventId() const { return event_id_; }
QString roomId() const { return room_id_; }
QString eventId() const { return event_id_; }
QString roomId() const { return room_id_; }
private:
QString event_id_;
QString room_id_;
QString event_id_;
QString room_id_;
ReadReceiptsModel model_;
ReadReceiptsModel model_;
};
#endif // READRECEIPTSMODEL_H

@ -33,496 +33,483 @@ Q_DECLARE_METATYPE(mtx::user_interactive::Auth)
RegisterPage::RegisterPage(QWidget *parent)
: QWidget(parent)
{
qRegisterMetaType<mtx::user_interactive::Unauthorized>();
qRegisterMetaType<mtx::user_interactive::Auth>();
top_layout_ = new QVBoxLayout();
back_layout_ = new QHBoxLayout();
back_layout_->setSpacing(0);
back_layout_->setContentsMargins(5, 5, -1, -1);
back_button_ = new FlatButton(this);
back_button_->setMinimumSize(QSize(30, 30));
QIcon icon;
icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png");
back_button_->setIcon(icon);
back_button_->setIconSize(QSize(32, 32));
back_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter);
back_layout_->addStretch(1);
QIcon logo;
logo.addFile(":/logos/register.png");
logo_ = new QLabel(this);
logo_->setPixmap(logo.pixmap(128));
logo_layout_ = new QHBoxLayout();
logo_layout_->setMargin(0);
logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter);
form_wrapper_ = new QHBoxLayout();
form_widget_ = new QWidget();
form_widget_->setMinimumSize(QSize(350, 300));
form_layout_ = new QVBoxLayout();
form_layout_->setSpacing(20);
form_layout_->setContentsMargins(0, 0, 0, 40);
form_widget_->setLayout(form_layout_);
form_wrapper_->addStretch(1);
form_wrapper_->addWidget(form_widget_);
form_wrapper_->addStretch(1);
username_input_ = new TextField();
username_input_->setLabel(tr("Username"));
username_input_->setRegexp(QRegularExpression("[a-z0-9._=/-]+"));
username_input_->setToolTip(tr("The username must not be empty, and must contain only the "
"characters a-z, 0-9, ., _, =, -, and /."));
password_input_ = new TextField();
password_input_->setLabel(tr("Password"));
password_input_->setRegexp(QRegularExpression("^.{8,}$"));
password_input_->setEchoMode(QLineEdit::Password);
password_input_->setToolTip(tr("Please choose a secure password. The exact requirements "
"for password strength may depend on your server."));
password_confirmation_ = new TextField();
password_confirmation_->setLabel(tr("Password confirmation"));
password_confirmation_->setEchoMode(QLineEdit::Password);
server_input_ = new TextField();
server_input_->setLabel(tr("Homeserver"));
server_input_->setRegexp(QRegularExpression(".+"));
server_input_->setToolTip(
tr("A server that allows registration. Since matrix is decentralized, you need to first "
"find a server you can register on or host your own."));
error_username_label_ = new QLabel(this);
error_username_label_->setWordWrap(true);
error_username_label_->hide();
error_password_label_ = new QLabel(this);
error_password_label_->setWordWrap(true);
error_password_label_->hide();
error_password_confirmation_label_ = new QLabel(this);
error_password_confirmation_label_->setWordWrap(true);
error_password_confirmation_label_->hide();
error_server_label_ = new QLabel(this);
error_server_label_->setWordWrap(true);
error_server_label_->hide();
form_layout_->addWidget(username_input_, Qt::AlignHCenter);
form_layout_->addWidget(error_username_label_, Qt::AlignHCenter);
form_layout_->addWidget(password_input_, Qt::AlignHCenter);
form_layout_->addWidget(error_password_label_, Qt::AlignHCenter);
form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter);
form_layout_->addWidget(error_password_confirmation_label_, Qt::AlignHCenter);
form_layout_->addWidget(server_input_, Qt::AlignHCenter);
form_layout_->addWidget(error_server_label_, Qt::AlignHCenter);
button_layout_ = new QHBoxLayout();
button_layout_->setSpacing(0);
button_layout_->setMargin(0);
error_label_ = new QLabel(this);
error_label_->setWordWrap(true);
register_button_ = new RaisedButton(tr("REGISTER"), this);
register_button_->setMinimumSize(350, 65);
register_button_->setFontSize(conf::btn::fontSize);
register_button_->setCornerRadius(conf::btn::cornerRadius);
button_layout_->addStretch(1);
button_layout_->addWidget(register_button_);
button_layout_->addStretch(1);
top_layout_->addLayout(back_layout_);
top_layout_->addLayout(logo_layout_);
top_layout_->addLayout(form_wrapper_);
top_layout_->addStretch(1);
top_layout_->addLayout(button_layout_);
top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter);
top_layout_->addStretch(1);
setLayout(top_layout_);
connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
connect(register_button_, SIGNAL(clicked()), this, SLOT(onRegisterButtonClicked()));
connect(username_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
connect(username_input_, &TextField::editingFinished, this, &RegisterPage::checkUsername);
connect(password_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
connect(password_input_, &TextField::editingFinished, this, &RegisterPage::checkPassword);
connect(password_confirmation_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
connect(password_confirmation_,
&TextField::editingFinished,
this,
&RegisterPage::checkPasswordConfirmation);
connect(server_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
connect(server_input_, &TextField::editingFinished, this, &RegisterPage::checkServer);
connect(
this,
&RegisterPage::serverError,
this,
[this](const QString &msg) {
server_input_->setValid(false);
showError(error_server_label_, msg);
},
Qt::QueuedConnection);
connect(this, &RegisterPage::wellKnownLookup, this, &RegisterPage::doWellKnownLookup);
connect(this, &RegisterPage::versionsCheck, this, &RegisterPage::doVersionsCheck);
connect(this, &RegisterPage::registration, this, &RegisterPage::doRegistration);
connect(this, &RegisterPage::UIA, this, &RegisterPage::doUIA);
connect(
this, &RegisterPage::registrationWithAuth, this, &RegisterPage::doRegistrationWithAuth);
qRegisterMetaType<mtx::user_interactive::Unauthorized>();
qRegisterMetaType<mtx::user_interactive::Auth>();
top_layout_ = new QVBoxLayout();
back_layout_ = new QHBoxLayout();
back_layout_->setSpacing(0);
back_layout_->setContentsMargins(5, 5, -1, -1);
back_button_ = new FlatButton(this);
back_button_->setMinimumSize(QSize(30, 30));
QIcon icon;
icon.addFile(":/icons/icons/ui/angle-pointing-to-left.png");
back_button_->setIcon(icon);
back_button_->setIconSize(QSize(32, 32));
back_layout_->addWidget(back_button_, 0, Qt::AlignLeft | Qt::AlignVCenter);
back_layout_->addStretch(1);
QIcon logo;
logo.addFile(":/logos/register.png");
logo_ = new QLabel(this);
logo_->setPixmap(logo.pixmap(128));
logo_layout_ = new QHBoxLayout();
logo_layout_->setMargin(0);
logo_layout_->addWidget(logo_, 0, Qt::AlignHCenter);
form_wrapper_ = new QHBoxLayout();
form_widget_ = new QWidget();
form_widget_->setMinimumSize(QSize(350, 300));
form_layout_ = new QVBoxLayout();
form_layout_->setSpacing(20);
form_layout_->setContentsMargins(0, 0, 0, 40);
form_widget_->setLayout(form_layout_);
form_wrapper_->addStretch(1);
form_wrapper_->addWidget(form_widget_);
form_wrapper_->addStretch(1);
username_input_ = new TextField();
username_input_->setLabel(tr("Username"));
username_input_->setRegexp(QRegularExpression("[a-z0-9._=/-]+"));
username_input_->setToolTip(tr("The username must not be empty, and must contain only the "
"characters a-z, 0-9, ., _, =, -, and /."));
password_input_ = new TextField();
password_input_->setLabel(tr("Password"));
password_input_->setRegexp(QRegularExpression("^.{8,}$"));
password_input_->setEchoMode(QLineEdit::Password);
password_input_->setToolTip(tr("Please choose a secure password. The exact requirements "
"for password strength may depend on your server."));
password_confirmation_ = new TextField();
password_confirmation_->setLabel(tr("Password confirmation"));
password_confirmation_->setEchoMode(QLineEdit::Password);
server_input_ = new TextField();
server_input_->setLabel(tr("Homeserver"));
server_input_->setRegexp(QRegularExpression(".+"));
server_input_->setToolTip(
tr("A server that allows registration. Since matrix is decentralized, you need to first "
"find a server you can register on or host your own."));
error_username_label_ = new QLabel(this);
error_username_label_->setWordWrap(true);
error_username_label_->hide();
error_password_label_ = new QLabel(this);
error_password_label_->setWordWrap(true);
error_password_label_->hide();
error_password_confirmation_label_ = new QLabel(this);
error_password_confirmation_label_->setWordWrap(true);
error_password_confirmation_label_->hide();
error_server_label_ = new QLabel(this);
error_server_label_->setWordWrap(true);
error_server_label_->hide();
form_layout_->addWidget(username_input_, Qt::AlignHCenter);
form_layout_->addWidget(error_username_label_, Qt::AlignHCenter);
form_layout_->addWidget(password_input_, Qt::AlignHCenter);
form_layout_->addWidget(error_password_label_, Qt::AlignHCenter);
form_layout_->addWidget(password_confirmation_, Qt::AlignHCenter);
form_layout_->addWidget(error_password_confirmation_label_, Qt::AlignHCenter);
form_layout_->addWidget(server_input_, Qt::AlignHCenter);
form_layout_->addWidget(error_server_label_, Qt::AlignHCenter);
button_layout_ = new QHBoxLayout();
button_layout_->setSpacing(0);
button_layout_->setMargin(0);
error_label_ = new QLabel(this);
error_label_->setWordWrap(true);
register_button_ = new RaisedButton(tr("REGISTER"), this);
register_button_->setMinimumSize(350, 65);
register_button_->setFontSize(conf::btn::fontSize);
register_button_->setCornerRadius(conf::btn::cornerRadius);
button_layout_->addStretch(1);
button_layout_->addWidget(register_button_);
button_layout_->addStretch(1);
top_layout_->addLayout(back_layout_);
top_layout_->addLayout(logo_layout_);
top_layout_->addLayout(form_wrapper_);
top_layout_->addStretch(1);
top_layout_->addLayout(button_layout_);
top_layout_->addWidget(error_label_, 0, Qt::AlignHCenter);
top_layout_->addStretch(1);
setLayout(top_layout_);
connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked()));
connect(register_button_, SIGNAL(clicked()), this, SLOT(onRegisterButtonClicked()));
connect(username_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
connect(username_input_, &TextField::editingFinished, this, &RegisterPage::checkUsername);
connect(password_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
connect(password_input_, &TextField::editingFinished, this, &RegisterPage::checkPassword);
connect(password_confirmation_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
connect(password_confirmation_,
&TextField::editingFinished,
this,
&RegisterPage::checkPasswordConfirmation);
connect(server_input_, SIGNAL(returnPressed()), register_button_, SLOT(click()));
connect(server_input_, &TextField::editingFinished, this, &RegisterPage::checkServer);
connect(
this,
&RegisterPage::serverError,
this,
[this](const QString &msg) {
server_input_->setValid(false);
showError(error_server_label_, msg);
},
Qt::QueuedConnection);
connect(this, &RegisterPage::wellKnownLookup, this, &RegisterPage::doWellKnownLookup);
connect(this, &RegisterPage::versionsCheck, this, &RegisterPage::doVersionsCheck);
connect(this, &RegisterPage::registration, this, &RegisterPage::doRegistration);
connect(this, &RegisterPage::UIA, this, &RegisterPage::doUIA);
connect(this, &RegisterPage::registrationWithAuth, this, &RegisterPage::doRegistrationWithAuth);
}
void
RegisterPage::onBackButtonClicked()
{
emit backButtonClicked();
emit backButtonClicked();
}
void
RegisterPage::showError(const QString &msg)
{
emit errorOccurred();
auto rect = QFontMetrics(font()).boundingRect(msg);
int width = rect.width();
int height = rect.height();
error_label_->setFixedHeight(qCeil(width / 200.0) * height);
error_label_->setText(msg);
emit errorOccurred();
auto rect = QFontMetrics(font()).boundingRect(msg);
int width = rect.width();
int height = rect.height();
error_label_->setFixedHeight(qCeil(width / 200.0) * height);
error_label_->setText(msg);
}
void
RegisterPage::showError(QLabel *label, const QString &msg)
{
emit errorOccurred();
auto rect = QFontMetrics(font()).boundingRect(msg);
int width = rect.width();
int height = rect.height();
label->setFixedHeight((int)qCeil(width / 200.0) * height);
label->setText(msg);
label->show();
emit errorOccurred();
auto rect = QFontMetrics(font()).boundingRect(msg);
int width = rect.width();
int height = rect.height();
label->setFixedHeight((int)qCeil(width / 200.0) * height);
label->setText(msg);
label->show();
}
bool
RegisterPage::checkOneField(QLabel *label, const TextField *t_field, const QString &msg)
{
if (t_field->isValid()) {
label->hide();
return true;
} else {
showError(label, msg);
return false;
}
if (t_field->isValid()) {
label->hide();
return true;
} else {
showError(label, msg);
return false;
}
}
bool
RegisterPage::checkUsername()
{
return checkOneField(error_username_label_,
username_input_,
tr("The username must not be empty, and must contain only the "
"characters a-z, 0-9, ., _, =, -, and /."));
return checkOneField(error_username_label_,
username_input_,
tr("The username must not be empty, and must contain only the "
"characters a-z, 0-9, ., _, =, -, and /."));
}
bool
RegisterPage::checkPassword()
{
return checkOneField(
error_password_label_, password_input_, tr("Password is not long enough (min 8 chars)"));
return checkOneField(
error_password_label_, password_input_, tr("Password is not long enough (min 8 chars)"));
}
bool
RegisterPage::checkPasswordConfirmation()
{
if (password_input_->text() == password_confirmation_->text()) {
error_password_confirmation_label_->hide();
password_confirmation_->setValid(true);
return true;
} else {
showError(error_password_confirmation_label_, tr("Passwords don't match"));
password_confirmation_->setValid(false);
return false;
}
if (password_input_->text() == password_confirmation_->text()) {
error_password_confirmation_label_->hide();
password_confirmation_->setValid(true);
return true;
} else {
showError(error_password_confirmation_label_, tr("Passwords don't match"));
password_confirmation_->setValid(false);
return false;
}
}
bool
RegisterPage::checkServer()
{
// This doesn't check that the server is reachable,
// just that the input is not obviously wrong.
return checkOneField(error_server_label_, server_input_, tr("Invalid server name"));
// This doesn't check that the server is reachable,
// just that the input is not obviously wrong.
return checkOneField(error_server_label_, server_input_, tr("Invalid server name"));
}
void
RegisterPage::onRegisterButtonClicked()
{
if (checkUsername() && checkPassword() && checkPasswordConfirmation() && checkServer()) {
auto server = server_input_->text().toStdString();
http::client()->set_server(server);
http::client()->verify_certificates(
!UserSettings::instance()->disableCertificateValidation());
// This starts a chain of `emit`s which ends up doing the
// registration. Signals are used rather than normal function
// calls so that the dialogs used in UIA work correctly.
//
// The sequence of events looks something like this:
//
// dowellKnownLookup
// v
// doVersionsCheck
// v
// doRegistration
// v
// doUIA <-----------------+
// v | More auth required
// doRegistrationWithAuth -+
// | Success
// v
// registering
emit wellKnownLookup();
emit registering();
}
if (checkUsername() && checkPassword() && checkPasswordConfirmation() && checkServer()) {
auto server = server_input_->text().toStdString();
http::client()->set_server(server);
http::client()->verify_certificates(
!UserSettings::instance()->disableCertificateValidation());
// This starts a chain of `emit`s which ends up doing the
// registration. Signals are used rather than normal function
// calls so that the dialogs used in UIA work correctly.
//
// The sequence of events looks something like this:
//
// dowellKnownLookup
// v
// doVersionsCheck
// v
// doRegistration
// v
// doUIA <-----------------+
// v | More auth required
// doRegistrationWithAuth -+
// | Success
// v
// registering
emit wellKnownLookup();
emit registering();
}
}
void
RegisterPage::doWellKnownLookup()
{
http::client()->well_known(
[this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) {
if (err) {
if (err->status_code == 404) {
nhlog::net()->info("Autodiscovery: No .well-known.");
// Check that the homeserver can be reached
emit versionsCheck();
return;
}
if (!err->parse_error.empty()) {
emit serverError(
tr("Autodiscovery failed. Received malformed response."));
nhlog::net()->error(
"Autodiscovery failed. Received malformed response.");
return;
}
emit serverError(tr("Autodiscovery failed. Unknown error when "
"requesting .well-known."));
nhlog::net()->error("Autodiscovery failed. Unknown error when "
"requesting .well-known. {} {}",
err->status_code,
err->error_code);
return;
}
nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'");
http::client()->set_server(res.homeserver.base_url);
http::client()->well_known(
[this](const mtx::responses::WellKnown &res, mtx::http::RequestErr err) {
if (err) {
if (err->status_code == 404) {
nhlog::net()->info("Autodiscovery: No .well-known.");
// Check that the homeserver can be reached
emit versionsCheck();
});
return;
}
if (!err->parse_error.empty()) {
emit serverError(tr("Autodiscovery failed. Received malformed response."));
nhlog::net()->error("Autodiscovery failed. Received malformed response.");
return;
}
emit serverError(tr("Autodiscovery failed. Unknown error when "
"requesting .well-known."));
nhlog::net()->error("Autodiscovery failed. Unknown error when "
"requesting .well-known. {} {}",
err->status_code,
err->error_code);
return;
}
nhlog::net()->info("Autodiscovery: Discovered '" + res.homeserver.base_url + "'");
http::client()->set_server(res.homeserver.base_url);
// Check that the homeserver can be reached
emit versionsCheck();
});
}
void
RegisterPage::doVersionsCheck()
{
// Make a request to /_matrix/client/versions to check the address
// given is a Matrix homeserver.
http::client()->versions(
[this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
if (err) {
if (err->status_code == 404) {
emit serverError(
tr("The required endpoints were not found. Possibly "
"not a Matrix server."));
return;
}
if (!err->parse_error.empty()) {
emit serverError(
tr("Received malformed response. Make sure the homeserver "
"domain is valid."));
return;
}
emit serverError(tr("An unknown error occured. Make sure the "
"homeserver domain is valid."));
return;
}
// Attempt registration without an `auth` dict
emit registration();
});
// Make a request to /_matrix/client/versions to check the address
// given is a Matrix homeserver.
http::client()->versions([this](const mtx::responses::Versions &, mtx::http::RequestErr err) {
if (err) {
if (err->status_code == 404) {
emit serverError(tr("The required endpoints were not found. Possibly "
"not a Matrix server."));
return;
}
if (!err->parse_error.empty()) {
emit serverError(tr("Received malformed response. Make sure the homeserver "
"domain is valid."));
return;
}
emit serverError(tr("An unknown error occured. Make sure the "
"homeserver domain is valid."));
return;
}
// Attempt registration without an `auth` dict
emit registration();
});
}
void
RegisterPage::doRegistration()
{
// These inputs should still be alright, but check just in case
if (checkUsername() && checkPassword() && checkPasswordConfirmation()) {
auto username = username_input_->text().toStdString();
auto password = password_input_->text().toStdString();
http::client()->registration(username, password, registrationCb());
}
// These inputs should still be alright, but check just in case
if (checkUsername() && checkPassword() && checkPasswordConfirmation()) {
auto username = username_input_->text().toStdString();
auto password = password_input_->text().toStdString();
http::client()->registration(username, password, registrationCb());
}
}
void
RegisterPage::doRegistrationWithAuth(const mtx::user_interactive::Auth &auth)
{
// These inputs should still be alright, but check just in case
if (checkUsername() && checkPassword() && checkPasswordConfirmation()) {
auto username = username_input_->text().toStdString();
auto password = password_input_->text().toStdString();
http::client()->registration(username, password, auth, registrationCb());
}
// These inputs should still be alright, but check just in case
if (checkUsername() && checkPassword() && checkPasswordConfirmation()) {
auto username = username_input_->text().toStdString();
auto password = password_input_->text().toStdString();
http::client()->registration(username, password, auth, registrationCb());
}
}
mtx::http::Callback<mtx::responses::Register>
RegisterPage::registrationCb()
{
// Return a function to be used as the callback when an attempt at
// registration is made.
return [this](const mtx::responses::Register &res, mtx::http::RequestErr err) {
if (!err) {
http::client()->set_user(res.user_id);
http::client()->set_access_token(res.access_token);
emit registerOk();
return;
}
// The server requires registration flows.
if (err->status_code == 401) {
if (err->matrix_error.unauthorized.flows.empty()) {
nhlog::net()->warn("failed to retrieve registration flows: "
"status_code({}), matrix_error({}) ",
static_cast<int>(err->status_code),
err->matrix_error.error);
showError(QString::fromStdString(err->matrix_error.error));
return;
}
// Attempt to complete a UIA stage
emit UIA(err->matrix_error.unauthorized);
return;
}
nhlog::net()->error("failed to register: status_code ({}), matrix_error({})",
static_cast<int>(err->status_code),
err->matrix_error.error);
// Return a function to be used as the callback when an attempt at
// registration is made.
return [this](const mtx::responses::Register &res, mtx::http::RequestErr err) {
if (!err) {
http::client()->set_user(res.user_id);
http::client()->set_access_token(res.access_token);
emit registerOk();
return;
}
// The server requires registration flows.
if (err->status_code == 401) {
if (err->matrix_error.unauthorized.flows.empty()) {
nhlog::net()->warn("failed to retrieve registration flows: "
"status_code({}), matrix_error({}) ",
static_cast<int>(err->status_code),
err->matrix_error.error);
showError(QString::fromStdString(err->matrix_error.error));
};
return;
}
// Attempt to complete a UIA stage
emit UIA(err->matrix_error.unauthorized);
return;
}
nhlog::net()->error("failed to register: status_code ({}), matrix_error({})",
static_cast<int>(err->status_code),
err->matrix_error.error);
showError(QString::fromStdString(err->matrix_error.error));
};
}
void
RegisterPage::doUIA(const mtx::user_interactive::Unauthorized &unauthorized)
{
auto completed_stages = unauthorized.completed;
auto flows = unauthorized.flows;
auto session =
unauthorized.session.empty() ? http::client()->generate_txn_id() : unauthorized.session;
nhlog::ui()->info("Completed stages: {}", completed_stages.size());
if (!completed_stages.empty()) {
// Get rid of all flows which don't start with the sequence of
// stages that have already been completed.
flows.erase(
std::remove_if(flows.begin(),
flows.end(),
[completed_stages](auto flow) {
if (completed_stages.size() > flow.stages.size())
return true;
for (size_t f = 0; f < completed_stages.size(); f++)
if (completed_stages[f] != flow.stages[f])
return true;
return false;
}),
flows.end());
}
auto completed_stages = unauthorized.completed;
auto flows = unauthorized.flows;
auto session =
unauthorized.session.empty() ? http::client()->generate_txn_id() : unauthorized.session;
nhlog::ui()->info("Completed stages: {}", completed_stages.size());
if (!completed_stages.empty()) {
// Get rid of all flows which don't start with the sequence of
// stages that have already been completed.
flows.erase(std::remove_if(flows.begin(),
flows.end(),
[completed_stages](auto flow) {
if (completed_stages.size() > flow.stages.size())
return true;
for (size_t f = 0; f < completed_stages.size(); f++)
if (completed_stages[f] != flow.stages[f])
return true;
return false;
}),
flows.end());
}
if (flows.empty()) {
nhlog::ui()->error("No available registration flows!");
showError(tr("No supported registration flows!"));
return;
}
auto current_stage = flows.front().stages.at(completed_stages.size());
if (current_stage == mtx::user_interactive::auth_types::recaptcha) {
auto captchaDialog = new dialogs::ReCaptcha(QString::fromStdString(session), this);
if (flows.empty()) {
nhlog::ui()->error("No available registration flows!");
showError(tr("No supported registration flows!"));
return;
}
connect(
captchaDialog, &dialogs::ReCaptcha::confirmation, this, [this, session, captchaDialog]() {
captchaDialog->close();
captchaDialog->deleteLater();
doRegistrationWithAuth(
mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Fallback{}});
});
auto current_stage = flows.front().stages.at(completed_stages.size());
if (current_stage == mtx::user_interactive::auth_types::recaptcha) {
auto captchaDialog = new dialogs::ReCaptcha(QString::fromStdString(session), this);
connect(captchaDialog,
&dialogs::ReCaptcha::confirmation,
this,
[this, session, captchaDialog]() {
captchaDialog->close();
captchaDialog->deleteLater();
doRegistrationWithAuth(mtx::user_interactive::Auth{
session, mtx::user_interactive::auth::Fallback{}});
});
connect(
captchaDialog, &dialogs::ReCaptcha::cancel, this, &RegisterPage::errorOccurred);
QTimer::singleShot(1000, this, [captchaDialog]() { captchaDialog->show(); });
} else if (current_stage == mtx::user_interactive::auth_types::dummy) {
doRegistrationWithAuth(
mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Dummy{}});
} else if (current_stage == mtx::user_interactive::auth_types::registration_token) {
bool ok;
QString token =
QInputDialog::getText(this,
tr("Registration token"),
tr("Please enter a valid registration token."),
QLineEdit::Normal,
QString(),
&ok);
if (ok) {
emit registrationWithAuth(mtx::user_interactive::Auth{
session,
mtx::user_interactive::auth::RegistrationToken{token.toStdString()}});
} else {
emit errorOccurred();
}
} else {
// use fallback
auto dialog = new dialogs::FallbackAuth(
QString::fromStdString(current_stage), QString::fromStdString(session), this);
connect(captchaDialog, &dialogs::ReCaptcha::cancel, this, &RegisterPage::errorOccurred);
connect(
dialog, &dialogs::FallbackAuth::confirmation, this, [this, session, dialog]() {
dialog->close();
dialog->deleteLater();
emit registrationWithAuth(mtx::user_interactive::Auth{
session, mtx::user_interactive::auth::Fallback{}});
});
QTimer::singleShot(1000, this, [captchaDialog]() { captchaDialog->show(); });
connect(dialog, &dialogs::FallbackAuth::cancel, this, &RegisterPage::errorOccurred);
} else if (current_stage == mtx::user_interactive::auth_types::dummy) {
doRegistrationWithAuth(
mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Dummy{}});
dialog->show();
} else if (current_stage == mtx::user_interactive::auth_types::registration_token) {
bool ok;
QString token = QInputDialog::getText(this,
tr("Registration token"),
tr("Please enter a valid registration token."),
QLineEdit::Normal,
QString(),
&ok);
if (ok) {
emit registrationWithAuth(mtx::user_interactive::Auth{
session, mtx::user_interactive::auth::RegistrationToken{token.toStdString()}});
} else {
emit errorOccurred();
}
} else {
// use fallback
auto dialog = new dialogs::FallbackAuth(
QString::fromStdString(current_stage), QString::fromStdString(session), this);
connect(dialog, &dialogs::FallbackAuth::confirmation, this, [this, session, dialog]() {
dialog->close();
dialog->deleteLater();
emit registrationWithAuth(
mtx::user_interactive::Auth{session, mtx::user_interactive::auth::Fallback{}});
});
connect(dialog, &dialogs::FallbackAuth::cancel, this, &RegisterPage::errorOccurred);
dialog->show();
}
}
void
RegisterPage::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}

@ -21,76 +21,76 @@ class QHBoxLayout;
class RegisterPage : public QWidget
{
Q_OBJECT
Q_OBJECT
public:
RegisterPage(QWidget *parent = nullptr);
RegisterPage(QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent *event) override;
void paintEvent(QPaintEvent *event) override;
signals:
void backButtonClicked();
void errorOccurred();
void backButtonClicked();
void errorOccurred();
//! Used to trigger the corresponding slot outside of the main thread.
void serverError(const QString &err);
//! Used to trigger the corresponding slot outside of the main thread.
void serverError(const QString &err);
void wellKnownLookup();
void versionsCheck();
void registration();
void UIA(const mtx::user_interactive::Unauthorized &unauthorized);
void registrationWithAuth(const mtx::user_interactive::Auth &auth);
void wellKnownLookup();
void versionsCheck();
void registration();
void UIA(const mtx::user_interactive::Unauthorized &unauthorized);
void registrationWithAuth(const mtx::user_interactive::Auth &auth);
void registering();
void registerOk();
void registering();
void registerOk();
private slots:
void onBackButtonClicked();
void onRegisterButtonClicked();
// function for showing different errors
void showError(const QString &msg);
void showError(QLabel *label, const QString &msg);
bool checkOneField(QLabel *label, const TextField *t_field, const QString &msg);
bool checkUsername();
bool checkPassword();
bool checkPasswordConfirmation();
bool checkServer();
void doWellKnownLookup();
void doVersionsCheck();
void doRegistration();
void doUIA(const mtx::user_interactive::Unauthorized &unauthorized);
void doRegistrationWithAuth(const mtx::user_interactive::Auth &auth);
mtx::http::Callback<mtx::responses::Register> registrationCb();
void onBackButtonClicked();
void onRegisterButtonClicked();
// function for showing different errors
void showError(const QString &msg);
void showError(QLabel *label, const QString &msg);
bool checkOneField(QLabel *label, const TextField *t_field, const QString &msg);
bool checkUsername();
bool checkPassword();
bool checkPasswordConfirmation();
bool checkServer();
void doWellKnownLookup();
void doVersionsCheck();
void doRegistration();
void doUIA(const mtx::user_interactive::Unauthorized &unauthorized);
void doRegistrationWithAuth(const mtx::user_interactive::Auth &auth);
mtx::http::Callback<mtx::responses::Register> registrationCb();
private:
QVBoxLayout *top_layout_;
QHBoxLayout *back_layout_;
QHBoxLayout *logo_layout_;
QHBoxLayout *button_layout_;
QLabel *logo_;
QLabel *error_label_;
QLabel *error_username_label_;
QLabel *error_password_label_;
QLabel *error_password_confirmation_label_;
QLabel *error_server_label_;
QLabel *error_registration_token_label_;
FlatButton *back_button_;
RaisedButton *register_button_;
QWidget *form_widget_;
QHBoxLayout *form_wrapper_;
QVBoxLayout *form_layout_;
TextField *username_input_;
TextField *password_input_;
TextField *password_confirmation_;
TextField *server_input_;
TextField *registration_token_input_;
QVBoxLayout *top_layout_;
QHBoxLayout *back_layout_;
QHBoxLayout *logo_layout_;
QHBoxLayout *button_layout_;
QLabel *logo_;
QLabel *error_label_;
QLabel *error_username_label_;
QLabel *error_password_label_;
QLabel *error_password_confirmation_label_;
QLabel *error_server_label_;
QLabel *error_registration_token_label_;
FlatButton *back_button_;
RaisedButton *register_button_;
QWidget *form_widget_;
QHBoxLayout *form_wrapper_;
QVBoxLayout *form_layout_;
TextField *username_input_;
TextField *password_input_;
TextField *password_confirmation_;
TextField *server_input_;
TextField *registration_token_input_;
};

@ -12,207 +12,205 @@ RoomDirectoryModel::RoomDirectoryModel(QObject *parent, const std::string &serve
: QAbstractListModel(parent)
, server_(server)
{
connect(ChatPage::instance(), &ChatPage::newRoom, this, [this](const QString &roomid) {
auto roomid_ = roomid.toStdString();
int i = 0;
for (const auto &room : publicRoomsData_) {
if (room.room_id == roomid_) {
emit dataChanged(index(i), index(i), {Roles::CanJoin});
break;
}
i++;
}
});
connect(this,
&RoomDirectoryModel::fetchedRoomsBatch,
this,
&RoomDirectoryModel::displayRooms,
Qt::QueuedConnection);
connect(ChatPage::instance(), &ChatPage::newRoom, this, [this](const QString &roomid) {
auto roomid_ = roomid.toStdString();
int i = 0;
for (const auto &room : publicRoomsData_) {
if (room.room_id == roomid_) {
emit dataChanged(index(i), index(i), {Roles::CanJoin});
break;
}
i++;
}
});
connect(this,
&RoomDirectoryModel::fetchedRoomsBatch,
this,
&RoomDirectoryModel::displayRooms,
Qt::QueuedConnection);
}
QHash<int, QByteArray>
RoomDirectoryModel::roleNames() const
{
return {
{Roles::Name, "name"},
{Roles::Id, "roomid"},
{Roles::AvatarUrl, "avatarUrl"},
{Roles::Topic, "topic"},
{Roles::MemberCount, "numMembers"},
{Roles::Previewable, "canPreview"},
{Roles::CanJoin, "canJoin"},
};
return {
{Roles::Name, "name"},
{Roles::Id, "roomid"},
{Roles::AvatarUrl, "avatarUrl"},
{Roles::Topic, "topic"},
{Roles::MemberCount, "numMembers"},
{Roles::Previewable, "canPreview"},
{Roles::CanJoin, "canJoin"},
};
}
void
RoomDirectoryModel::resetDisplayedData()
{
beginResetModel();
beginResetModel();
prevBatch_ = "";
nextBatch_ = "";
canFetchMore_ = true;
prevBatch_ = "";
nextBatch_ = "";
canFetchMore_ = true;
publicRoomsData_.clear();
publicRoomsData_.clear();
endResetModel();
endResetModel();
}
void
RoomDirectoryModel::setMatrixServer(const QString &s)
{
server_ = s.toStdString();
server_ = s.toStdString();
nhlog::ui()->debug("Received matrix server: {}", server_);
nhlog::ui()->debug("Received matrix server: {}", server_);
resetDisplayedData();
resetDisplayedData();
}
void
RoomDirectoryModel::setSearchTerm(const QString &f)
{
userSearchString_ = f.toStdString();
userSearchString_ = f.toStdString();
nhlog::ui()->debug("Received user query: {}", userSearchString_);
nhlog::ui()->debug("Received user query: {}", userSearchString_);
resetDisplayedData();
resetDisplayedData();
}
bool
RoomDirectoryModel::canJoinRoom(const QString &room) const
{
return !room.isEmpty() && cache::getRoomInfo({room.toStdString()}).empty();
return !room.isEmpty() && cache::getRoomInfo({room.toStdString()}).empty();
}
std::vector<std::string>
RoomDirectoryModel::getViasForRoom(const std::vector<std::string> &aliases)
{
std::vector<std::string> vias;
vias.reserve(aliases.size());
std::transform(aliases.begin(),
aliases.end(),
std::back_inserter(vias),
[](const auto &alias) { return alias.substr(alias.find(":") + 1); });
// When joining a room hosted on a homeserver other than the one the
// account has been registered on, the room's server has to be explicitly
// specified in the "server_name=..." URL parameter of the Matrix Join Room
// request. For more details consult the specs:
// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-join-roomidoralias
if (!server_.empty()) {
vias.push_back(server_);
}
std::vector<std::string> vias;
vias.reserve(aliases.size());
std::transform(aliases.begin(), aliases.end(), std::back_inserter(vias), [](const auto &alias) {
return alias.substr(alias.find(":") + 1);
});
return vias;
// When joining a room hosted on a homeserver other than the one the
// account has been registered on, the room's server has to be explicitly
// specified in the "server_name=..." URL parameter of the Matrix Join Room
// request. For more details consult the specs:
// https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-join-roomidoralias
if (!server_.empty()) {
vias.push_back(server_);
}
return vias;
}
void
RoomDirectoryModel::joinRoom(const int &index)
{
if (index >= 0 && static_cast<size_t>(index) < publicRoomsData_.size()) {
const auto &chunk = publicRoomsData_[index];
nhlog::ui()->debug("'Joining room {}", chunk.room_id);
ChatPage::instance()->joinRoomVia(chunk.room_id, getViasForRoom(chunk.aliases));
}
if (index >= 0 && static_cast<size_t>(index) < publicRoomsData_.size()) {
const auto &chunk = publicRoomsData_[index];
nhlog::ui()->debug("'Joining room {}", chunk.room_id);
ChatPage::instance()->joinRoomVia(chunk.room_id, getViasForRoom(chunk.aliases));
}
}
QVariant
RoomDirectoryModel::data(const QModelIndex &index, int role) const
{
if (hasIndex(index.row(), index.column(), index.parent())) {
const auto &room_chunk = publicRoomsData_[index.row()];
switch (role) {
case Roles::Name:
return QString::fromStdString(room_chunk.name);
case Roles::Id:
return QString::fromStdString(room_chunk.room_id);
case Roles::AvatarUrl:
return QString::fromStdString(room_chunk.avatar_url);
case Roles::Topic:
return QString::fromStdString(room_chunk.topic);
case Roles::MemberCount:
return QVariant::fromValue(room_chunk.num_joined_members);
case Roles::Previewable:
return QVariant::fromValue(room_chunk.world_readable);
case Roles::CanJoin:
return canJoinRoom(QString::fromStdString(room_chunk.room_id));
}
if (hasIndex(index.row(), index.column(), index.parent())) {
const auto &room_chunk = publicRoomsData_[index.row()];
switch (role) {
case Roles::Name:
return QString::fromStdString(room_chunk.name);
case Roles::Id:
return QString::fromStdString(room_chunk.room_id);
case Roles::AvatarUrl:
return QString::fromStdString(room_chunk.avatar_url);
case Roles::Topic:
return QString::fromStdString(room_chunk.topic);
case Roles::MemberCount:
return QVariant::fromValue(room_chunk.num_joined_members);
case Roles::Previewable:
return QVariant::fromValue(room_chunk.world_readable);
case Roles::CanJoin:
return canJoinRoom(QString::fromStdString(room_chunk.room_id));
}
return {};
}
return {};
}
void
RoomDirectoryModel::fetchMore(const QModelIndex &)
{
if (!canFetchMore_)
return;
nhlog::net()->debug("Fetching more rooms from mtxclient...");
mtx::requests::PublicRooms req;
req.limit = limit_;
req.since = prevBatch_;
req.filter.generic_search_term = userSearchString_;
// req.third_party_instance_id = third_party_instance_id;
auto requested_server = server_;
reachedEndOfPagination_ = false;
emit reachedEndOfPaginationChanged();
loadingMoreRooms_ = true;
emit loadingMoreRoomsChanged();
http::client()->post_public_rooms(
req,
[requested_server, this, req](const mtx::responses::PublicRooms &res,
mtx::http::RequestErr err) {
loadingMoreRooms_ = false;
emit loadingMoreRoomsChanged();
if (err) {
nhlog::net()->error(
"Failed to retrieve rooms from mtxclient - {} - {} - {}",
mtx::errors::to_string(err->matrix_error.errcode),
err->matrix_error.error,
err->parse_error);
} else if (req.filter.generic_search_term == this->userSearchString_ &&
req.since == this->prevBatch_ && requested_server == this->server_) {
nhlog::net()->debug("signalling chunk to GUI thread");
emit fetchedRoomsBatch(res.chunk, res.next_batch);
}
},
requested_server);
if (!canFetchMore_)
return;
nhlog::net()->debug("Fetching more rooms from mtxclient...");
mtx::requests::PublicRooms req;
req.limit = limit_;
req.since = prevBatch_;
req.filter.generic_search_term = userSearchString_;
// req.third_party_instance_id = third_party_instance_id;
auto requested_server = server_;
reachedEndOfPagination_ = false;
emit reachedEndOfPaginationChanged();
loadingMoreRooms_ = true;
emit loadingMoreRoomsChanged();
http::client()->post_public_rooms(
req,
[requested_server, this, req](const mtx::responses::PublicRooms &res,
mtx::http::RequestErr err) {
loadingMoreRooms_ = false;
emit loadingMoreRoomsChanged();
if (err) {
nhlog::net()->error("Failed to retrieve rooms from mtxclient - {} - {} - {}",
mtx::errors::to_string(err->matrix_error.errcode),
err->matrix_error.error,
err->parse_error);
} else if (req.filter.generic_search_term == this->userSearchString_ &&
req.since == this->prevBatch_ && requested_server == this->server_) {
nhlog::net()->debug("signalling chunk to GUI thread");
emit fetchedRoomsBatch(res.chunk, res.next_batch);
}
},
requested_server);
}
void
RoomDirectoryModel::displayRooms(std::vector<mtx::responses::PublicRoomsChunk> fetched_rooms,
const std::string &next_batch)
{
nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, next_batch);
if (fetched_rooms.empty()) {
nhlog::net()->error("mtxclient helper thread yielded empty chunk!");
return;
}
beginInsertRows(QModelIndex(),
static_cast<int>(publicRoomsData_.size()),
static_cast<int>(publicRoomsData_.size() + fetched_rooms.size()) - 1);
this->publicRoomsData_.insert(
this->publicRoomsData_.end(), fetched_rooms.begin(), fetched_rooms.end());
endInsertRows();
if (next_batch.empty()) {
canFetchMore_ = false;
reachedEndOfPagination_ = true;
emit reachedEndOfPaginationChanged();
}
nhlog::net()->debug("Prev batch: {} | Next batch: {}", prevBatch_, next_batch);
if (fetched_rooms.empty()) {
nhlog::net()->error("mtxclient helper thread yielded empty chunk!");
return;
}
beginInsertRows(QModelIndex(),
static_cast<int>(publicRoomsData_.size()),
static_cast<int>(publicRoomsData_.size() + fetched_rooms.size()) - 1);
this->publicRoomsData_.insert(
this->publicRoomsData_.end(), fetched_rooms.begin(), fetched_rooms.end());
endInsertRows();
if (next_batch.empty()) {
canFetchMore_ = false;
reachedEndOfPagination_ = true;
emit reachedEndOfPaginationChanged();
}
prevBatch_ = next_batch;
prevBatch_ = next_batch;
nhlog::ui()->debug("Finished loading rooms");
nhlog::ui()->debug("Finished loading rooms");
}

@ -25,74 +25,74 @@ struct PublicRooms;
class RoomDirectoryModel : public QAbstractListModel
{
Q_OBJECT
Q_OBJECT
Q_PROPERTY(bool loadingMoreRooms READ loadingMoreRooms NOTIFY loadingMoreRoomsChanged)
Q_PROPERTY(bool reachedEndOfPagination READ reachedEndOfPagination NOTIFY
reachedEndOfPaginationChanged)
Q_PROPERTY(bool loadingMoreRooms READ loadingMoreRooms NOTIFY loadingMoreRoomsChanged)
Q_PROPERTY(
bool reachedEndOfPagination READ reachedEndOfPagination NOTIFY reachedEndOfPaginationChanged)
public:
explicit RoomDirectoryModel(QObject *parent = nullptr, const std::string &server = "");
explicit RoomDirectoryModel(QObject *parent = nullptr, const std::string &server = "");
enum Roles
{
Name = Qt::UserRole,
Id,
AvatarUrl,
Topic,
MemberCount,
Previewable,
CanJoin,
};
QHash<int, QByteArray> roleNames() const override;
enum Roles
{
Name = Qt::UserRole,
Id,
AvatarUrl,
Topic,
MemberCount,
Previewable,
CanJoin,
};
QHash<int, QByteArray> roleNames() const override;
QVariant data(const QModelIndex &index, int role) const override;
QVariant data(const QModelIndex &index, int role) const override;
inline int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
(void)parent;
return static_cast<int>(publicRoomsData_.size());
}
inline int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
(void)parent;
return static_cast<int>(publicRoomsData_.size());
}
bool canFetchMore(const QModelIndex &) const override { return canFetchMore_; }
bool canFetchMore(const QModelIndex &) const override { return canFetchMore_; }
bool loadingMoreRooms() const { return loadingMoreRooms_; }
bool loadingMoreRooms() const { return loadingMoreRooms_; }
bool reachedEndOfPagination() const { return reachedEndOfPagination_; }
bool reachedEndOfPagination() const { return reachedEndOfPagination_; }
void fetchMore(const QModelIndex &) override;
void fetchMore(const QModelIndex &) override;
Q_INVOKABLE void joinRoom(const int &index = -1);
Q_INVOKABLE void joinRoom(const int &index = -1);
signals:
void fetchedRoomsBatch(std::vector<mtx::responses::PublicRoomsChunk> rooms,
const std::string &next_batch);
void loadingMoreRoomsChanged();
void reachedEndOfPaginationChanged();
void fetchedRoomsBatch(std::vector<mtx::responses::PublicRoomsChunk> rooms,
const std::string &next_batch);
void loadingMoreRoomsChanged();
void reachedEndOfPaginationChanged();
public slots:
void setMatrixServer(const QString &s = "");
void setSearchTerm(const QString &f);
void setMatrixServer(const QString &s = "");
void setSearchTerm(const QString &f);
private slots:
void displayRooms(std::vector<mtx::responses::PublicRoomsChunk> rooms,
const std::string &next_batch);
void displayRooms(std::vector<mtx::responses::PublicRoomsChunk> rooms,
const std::string &next_batch);
private:
bool canJoinRoom(const QString &room) const;
bool canJoinRoom(const QString &room) const;
static constexpr size_t limit_ = 50;
static constexpr size_t limit_ = 50;
std::string server_;
std::string userSearchString_;
std::string prevBatch_;
std::string nextBatch_;
bool canFetchMore_{true};
bool loadingMoreRooms_{false};
bool reachedEndOfPagination_{false};
std::vector<mtx::responses::PublicRoomsChunk> publicRoomsData_;
std::string server_;
std::string userSearchString_;
std::string prevBatch_;
std::string nextBatch_;
bool canFetchMore_{true};
bool loadingMoreRooms_{false};
bool reachedEndOfPagination_{false};
std::vector<mtx::responses::PublicRoomsChunk> publicRoomsData_;
std::vector<std::string> getViasForRoom(const std::vector<std::string> &room);
void resetDisplayedData();
std::vector<std::string> getViasForRoom(const std::vector<std::string> &room);
void resetDisplayedData();
};

@ -14,71 +14,67 @@ RoomsModel::RoomsModel(bool showOnlyRoomWithAliases, QObject *parent)
: QAbstractListModel(parent)
, showOnlyRoomWithAliases_(showOnlyRoomWithAliases)
{
std::vector<std::string> rooms_ = cache::joinedRooms();
roomInfos = cache::getRoomInfo(rooms_);
if (!showOnlyRoomWithAliases_) {
roomids.reserve(rooms_.size());
roomAliases.reserve(rooms_.size());
}
std::vector<std::string> rooms_ = cache::joinedRooms();
roomInfos = cache::getRoomInfo(rooms_);
if (!showOnlyRoomWithAliases_) {
roomids.reserve(rooms_.size());
roomAliases.reserve(rooms_.size());
}
for (const auto &r : rooms_) {
auto roomAliasesList = cache::client()->getRoomAliases(r);
for (const auto &r : rooms_) {
auto roomAliasesList = cache::client()->getRoomAliases(r);
if (showOnlyRoomWithAliases_) {
if (roomAliasesList && !roomAliasesList->alias.empty()) {
roomids.push_back(QString::fromStdString(r));
roomAliases.push_back(
QString::fromStdString(roomAliasesList->alias));
}
} else {
roomids.push_back(QString::fromStdString(r));
roomAliases.push_back(
roomAliasesList ? QString::fromStdString(roomAliasesList->alias) : "");
}
if (showOnlyRoomWithAliases_) {
if (roomAliasesList && !roomAliasesList->alias.empty()) {
roomids.push_back(QString::fromStdString(r));
roomAliases.push_back(QString::fromStdString(roomAliasesList->alias));
}
} else {
roomids.push_back(QString::fromStdString(r));
roomAliases.push_back(roomAliasesList ? QString::fromStdString(roomAliasesList->alias)
: "");
}
}
}
QHash<int, QByteArray>
RoomsModel::roleNames() const
{
return {{CompletionModel::CompletionRole, "completionRole"},
{CompletionModel::SearchRole, "searchRole"},
{CompletionModel::SearchRole2, "searchRole2"},
{Roles::RoomAlias, "roomAlias"},
{Roles::AvatarUrl, "avatarUrl"},
{Roles::RoomID, "roomid"},
{Roles::RoomName, "roomName"}};
return {{CompletionModel::CompletionRole, "completionRole"},
{CompletionModel::SearchRole, "searchRole"},
{CompletionModel::SearchRole2, "searchRole2"},
{Roles::RoomAlias, "roomAlias"},
{Roles::AvatarUrl, "avatarUrl"},
{Roles::RoomID, "roomid"},
{Roles::RoomName, "roomName"}};
}
QVariant
RoomsModel::data(const QModelIndex &index, int role) const
{
if (hasIndex(index.row(), index.column(), index.parent())) {
switch (role) {
case CompletionModel::CompletionRole: {
if (UserSettings::instance()->markdown()) {
QString percentEncoding =
QUrl::toPercentEncoding(roomAliases[index.row()]);
return QString("[%1](https://matrix.to/#/%2)")
.arg(roomAliases[index.row()], percentEncoding);
} else {
return roomAliases[index.row()];
}
}
case CompletionModel::SearchRole:
case Qt::DisplayRole:
case Roles::RoomAlias:
return roomAliases[index.row()].toHtmlEscaped();
case CompletionModel::SearchRole2:
case Roles::RoomName:
return QString::fromStdString(roomInfos.at(roomids[index.row()]).name)
.toHtmlEscaped();
case Roles::AvatarUrl:
return QString::fromStdString(
roomInfos.at(roomids[index.row()]).avatar_url);
case Roles::RoomID:
return roomids[index.row()].toHtmlEscaped();
}
if (hasIndex(index.row(), index.column(), index.parent())) {
switch (role) {
case CompletionModel::CompletionRole: {
if (UserSettings::instance()->markdown()) {
QString percentEncoding = QUrl::toPercentEncoding(roomAliases[index.row()]);
return QString("[%1](https://matrix.to/#/%2)")
.arg(roomAliases[index.row()], percentEncoding);
} else {
return roomAliases[index.row()];
}
}
case CompletionModel::SearchRole:
case Qt::DisplayRole:
case Roles::RoomAlias:
return roomAliases[index.row()].toHtmlEscaped();
case CompletionModel::SearchRole2:
case Roles::RoomName:
return QString::fromStdString(roomInfos.at(roomids[index.row()]).name).toHtmlEscaped();
case Roles::AvatarUrl:
return QString::fromStdString(roomInfos.at(roomids[index.row()]).avatar_url);
case Roles::RoomID:
return roomids[index.row()].toHtmlEscaped();
}
return {};
}
return {};
}

@ -12,26 +12,26 @@
class RoomsModel : public QAbstractListModel
{
public:
enum Roles
{
AvatarUrl = Qt::UserRole,
RoomAlias,
RoomID,
RoomName,
};
enum Roles
{
AvatarUrl = Qt::UserRole,
RoomAlias,
RoomID,
RoomName,
};
RoomsModel(bool showOnlyRoomWithAliases = false, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
(void)parent;
return (int)roomids.size();
}
QVariant data(const QModelIndex &index, int role) const override;
RoomsModel(bool showOnlyRoomWithAliases = false, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
(void)parent;
return (int)roomids.size();
}
QVariant data(const QModelIndex &index, int role) const override;
private:
std::vector<QString> roomids;
std::vector<QString> roomAliases;
std::map<QString, RoomInfo> roomInfos;
bool showOnlyRoomWithAliases_;
std::vector<QString> roomids;
std::vector<QString> roomAliases;
std::map<QString, RoomInfo> roomInfos;
bool showOnlyRoomWithAliases_;
};

@ -12,46 +12,46 @@
SSOHandler::SSOHandler(QObject *)
{
QTimer::singleShot(120000, this, &SSOHandler::ssoFailed);
using namespace httplib;
svr.set_logger([](const Request &req, const Response &res) {
nhlog::net()->info("req: {}, res: {}", req.path, res.status);
});
svr.Get("/sso", [this](const Request &req, Response &res) {
if (req.has_param("loginToken")) {
auto val = req.get_param_value("loginToken");
res.set_content("SSO success", "text/plain");
emit ssoSuccess(val);
} else {
res.set_content("Missing loginToken for SSO login!", "text/plain");
emit ssoFailed();
}
});
std::thread t([this]() {
this->port = svr.bind_to_any_port("localhost");
svr.listen_after_bind();
});
t.detach();
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
QTimer::singleShot(120000, this, &SSOHandler::ssoFailed);
using namespace httplib;
svr.set_logger([](const Request &req, const Response &res) {
nhlog::net()->info("req: {}, res: {}", req.path, res.status);
});
svr.Get("/sso", [this](const Request &req, Response &res) {
if (req.has_param("loginToken")) {
auto val = req.get_param_value("loginToken");
res.set_content("SSO success", "text/plain");
emit ssoSuccess(val);
} else {
res.set_content("Missing loginToken for SSO login!", "text/plain");
emit ssoFailed();
}
});
std::thread t([this]() {
this->port = svr.bind_to_any_port("localhost");
svr.listen_after_bind();
});
t.detach();
while (!svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
SSOHandler::~SSOHandler()
{
svr.stop();
while (svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
svr.stop();
while (svr.is_running()) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
std::string
SSOHandler::url() const
{
return "http://localhost:" + std::to_string(port) + "/sso";
return "http://localhost:" + std::to_string(port) + "/sso";
}

@ -9,20 +9,20 @@
class SSOHandler : public QObject
{
Q_OBJECT
Q_OBJECT
public:
SSOHandler(QObject *parent = nullptr);
SSOHandler(QObject *parent = nullptr);
~SSOHandler();
~SSOHandler();
std::string url() const;
std::string url() const;
signals:
void ssoSuccess(std::string token);
void ssoFailed();
void ssoSuccess(std::string token);
void ssoFailed();
private:
httplib::Server svr;
int port = 0;
httplib::Server svr;
int port = 0;
};

@ -24,344 +24,336 @@ SingleImagePackModel::SingleImagePackModel(ImagePackInfo pack_, QObject *parent)
, old_statekey_(statekey_)
, pack(std::move(pack_.pack))
{
[[maybe_unused]] static auto imageInfoType = qRegisterMetaType<mtx::common::ImageInfo>();
[[maybe_unused]] static auto imageInfoType = qRegisterMetaType<mtx::common::ImageInfo>();
if (!pack.pack)
pack.pack = mtx::events::msc2545::ImagePack::PackDescription{};
if (!pack.pack)
pack.pack = mtx::events::msc2545::ImagePack::PackDescription{};
for (const auto &e : pack.images)
shortcodes.push_back(e.first);
for (const auto &e : pack.images)
shortcodes.push_back(e.first);
connect(this, &SingleImagePackModel::addImage, this, &SingleImagePackModel::addImageCb);
connect(this, &SingleImagePackModel::addImage, this, &SingleImagePackModel::addImageCb);
}
int
SingleImagePackModel::rowCount(const QModelIndex &) const
{
return (int)shortcodes.size();
return (int)shortcodes.size();
}
QHash<int, QByteArray>
SingleImagePackModel::roleNames() const
{
return {
{Roles::Url, "url"},
{Roles::ShortCode, "shortCode"},
{Roles::Body, "body"},
{Roles::IsEmote, "isEmote"},
{Roles::IsSticker, "isSticker"},
};
return {
{Roles::Url, "url"},
{Roles::ShortCode, "shortCode"},
{Roles::Body, "body"},
{Roles::IsEmote, "isEmote"},
{Roles::IsSticker, "isSticker"},
};
}
QVariant
SingleImagePackModel::data(const QModelIndex &index, int role) const
{
if (hasIndex(index.row(), index.column(), index.parent())) {
const auto &img = pack.images.at(shortcodes.at(index.row()));
switch (role) {
case Url:
return QString::fromStdString(img.url);
case ShortCode:
return QString::fromStdString(shortcodes.at(index.row()));
case Body:
return QString::fromStdString(img.body);
case IsEmote:
return img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji();
case IsSticker:
return img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker();
default:
return {};
}
if (hasIndex(index.row(), index.column(), index.parent())) {
const auto &img = pack.images.at(shortcodes.at(index.row()));
switch (role) {
case Url:
return QString::fromStdString(img.url);
case ShortCode:
return QString::fromStdString(shortcodes.at(index.row()));
case Body:
return QString::fromStdString(img.body);
case IsEmote:
return img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji();
case IsSticker:
return img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker();
default:
return {};
}
return {};
}
return {};
}
bool
SingleImagePackModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
using mtx::events::msc2545::PackUsage;
if (hasIndex(index.row(), index.column(), index.parent())) {
auto &img = pack.images.at(shortcodes.at(index.row()));
switch (role) {
case ShortCode: {
auto newCode = value.toString().toStdString();
// otherwise we delete this by accident
if (pack.images.count(newCode))
return false;
auto tmp = img;
auto oldCode = shortcodes.at(index.row());
pack.images.erase(oldCode);
shortcodes[index.row()] = newCode;
pack.images.insert({newCode, tmp});
emit dataChanged(
this->index(index.row()), this->index(index.row()), {Roles::ShortCode});
return true;
}
case Body:
img.body = value.toString().toStdString();
emit dataChanged(
this->index(index.row()), this->index(index.row()), {Roles::Body});
return true;
case IsEmote: {
bool isEmote = value.toBool();
bool isSticker =
img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker();
img.usage.set(PackUsage::Emoji, isEmote);
img.usage.set(PackUsage::Sticker, isSticker);
if (img.usage == pack.pack->usage)
img.usage.reset();
emit dataChanged(
this->index(index.row()), this->index(index.row()), {Roles::IsEmote});
return true;
}
case IsSticker: {
bool isEmote =
img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji();
bool isSticker = value.toBool();
img.usage.set(PackUsage::Emoji, isEmote);
img.usage.set(PackUsage::Sticker, isSticker);
if (img.usage == pack.pack->usage)
img.usage.reset();
emit dataChanged(
this->index(index.row()), this->index(index.row()), {Roles::IsSticker});
return true;
}
}
using mtx::events::msc2545::PackUsage;
if (hasIndex(index.row(), index.column(), index.parent())) {
auto &img = pack.images.at(shortcodes.at(index.row()));
switch (role) {
case ShortCode: {
auto newCode = value.toString().toStdString();
// otherwise we delete this by accident
if (pack.images.count(newCode))
return false;
auto tmp = img;
auto oldCode = shortcodes.at(index.row());
pack.images.erase(oldCode);
shortcodes[index.row()] = newCode;
pack.images.insert({newCode, tmp});
emit dataChanged(
this->index(index.row()), this->index(index.row()), {Roles::ShortCode});
return true;
}
return false;
case Body:
img.body = value.toString().toStdString();
emit dataChanged(this->index(index.row()), this->index(index.row()), {Roles::Body});
return true;
case IsEmote: {
bool isEmote = value.toBool();
bool isSticker = img.overrides_usage() ? img.is_sticker() : pack.pack->is_sticker();
img.usage.set(PackUsage::Emoji, isEmote);
img.usage.set(PackUsage::Sticker, isSticker);
if (img.usage == pack.pack->usage)
img.usage.reset();
emit dataChanged(this->index(index.row()), this->index(index.row()), {Roles::IsEmote});
return true;
}
case IsSticker: {
bool isEmote = img.overrides_usage() ? img.is_emoji() : pack.pack->is_emoji();
bool isSticker = value.toBool();
img.usage.set(PackUsage::Emoji, isEmote);
img.usage.set(PackUsage::Sticker, isSticker);
if (img.usage == pack.pack->usage)
img.usage.reset();
emit dataChanged(
this->index(index.row()), this->index(index.row()), {Roles::IsSticker});
return true;
}
}
}
return false;
}
bool
SingleImagePackModel::isGloballyEnabled() const
{
if (auto roomPacks =
cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) {
if (auto tmp = std::get_if<
mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>(
&*roomPacks)) {
if (tmp->content.rooms.count(roomid_) &&
tmp->content.rooms.at(roomid_).count(statekey_))
return true;
}
if (auto roomPacks = cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) {
if (auto tmp =
std::get_if<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>(
&*roomPacks)) {
if (tmp->content.rooms.count(roomid_) &&
tmp->content.rooms.at(roomid_).count(statekey_))
return true;
}
return false;
}
return false;
}
void
SingleImagePackModel::setGloballyEnabled(bool enabled)
{
mtx::events::msc2545::ImagePackRooms content{};
if (auto roomPacks =
cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) {
if (auto tmp = std::get_if<
mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>(
&*roomPacks)) {
content = tmp->content;
}
mtx::events::msc2545::ImagePackRooms content{};
if (auto roomPacks = cache::client()->getAccountData(mtx::events::EventType::ImagePackRooms)) {
if (auto tmp =
std::get_if<mtx::events::EphemeralEvent<mtx::events::msc2545::ImagePackRooms>>(
&*roomPacks)) {
content = tmp->content;
}
}
if (enabled)
content.rooms[roomid_][statekey_] = {};
else
content.rooms[roomid_].erase(statekey_);
if (enabled)
content.rooms[roomid_][statekey_] = {};
else
content.rooms[roomid_].erase(statekey_);
http::client()->put_account_data(content, [](mtx::http::RequestErr) {
// emit this->globallyEnabledChanged();
});
http::client()->put_account_data(content, [](mtx::http::RequestErr) {
// emit this->globallyEnabledChanged();
});
}
bool
SingleImagePackModel::canEdit() const
{
if (roomid_.empty())
return true;
else
return Permissions(QString::fromStdString(roomid_))
.canChange(qml_mtx_events::ImagePackInRoom);
if (roomid_.empty())
return true;
else
return Permissions(QString::fromStdString(roomid_))
.canChange(qml_mtx_events::ImagePackInRoom);
}
void
SingleImagePackModel::setPackname(QString val)
{
auto val_ = val.toStdString();
if (val_ != this->pack.pack->display_name) {
this->pack.pack->display_name = val_;
emit packnameChanged();
}
auto val_ = val.toStdString();
if (val_ != this->pack.pack->display_name) {
this->pack.pack->display_name = val_;
emit packnameChanged();
}
}
void
SingleImagePackModel::setAttribution(QString val)
{
auto val_ = val.toStdString();
if (val_ != this->pack.pack->attribution) {
this->pack.pack->attribution = val_;
emit attributionChanged();
}
auto val_ = val.toStdString();
if (val_ != this->pack.pack->attribution) {
this->pack.pack->attribution = val_;
emit attributionChanged();
}
}
void
SingleImagePackModel::setAvatarUrl(QString val)
{
auto val_ = val.toStdString();
if (val_ != this->pack.pack->avatar_url) {
this->pack.pack->avatar_url = val_;
emit avatarUrlChanged();
}
auto val_ = val.toStdString();
if (val_ != this->pack.pack->avatar_url) {
this->pack.pack->avatar_url = val_;
emit avatarUrlChanged();
}
}
void
SingleImagePackModel::setStatekey(QString val)
{
auto val_ = val.toStdString();
if (val_ != statekey_) {
statekey_ = val_;
emit statekeyChanged();
}
auto val_ = val.toStdString();
if (val_ != statekey_) {
statekey_ = val_;
emit statekeyChanged();
}
}
void
SingleImagePackModel::setIsStickerPack(bool val)
{
using mtx::events::msc2545::PackUsage;
if (val != pack.pack->is_sticker()) {
pack.pack->usage.set(PackUsage::Sticker, val);
emit isStickerPackChanged();
}
using mtx::events::msc2545::PackUsage;
if (val != pack.pack->is_sticker()) {
pack.pack->usage.set(PackUsage::Sticker, val);
emit isStickerPackChanged();
}
}
void
SingleImagePackModel::setIsEmotePack(bool val)
{
using mtx::events::msc2545::PackUsage;
if (val != pack.pack->is_emoji()) {
pack.pack->usage.set(PackUsage::Emoji, val);
emit isEmotePackChanged();
}
using mtx::events::msc2545::PackUsage;
if (val != pack.pack->is_emoji()) {
pack.pack->usage.set(PackUsage::Emoji, val);
emit isEmotePackChanged();
}
}
void
SingleImagePackModel::save()
{
if (roomid_.empty()) {
http::client()->put_account_data(pack, [](mtx::http::RequestErr e) {
if (e)
ChatPage::instance()->showNotification(
tr("Failed to update image pack: %1")
.arg(QString::fromStdString(e->matrix_error.error)));
});
} else {
if (old_statekey_ != statekey_) {
http::client()->send_state_event(
roomid_,
to_string(mtx::events::EventType::ImagePackInRoom),
old_statekey_,
nlohmann::json::object(),
[](const mtx::responses::EventId &, mtx::http::RequestErr e) {
if (e)
ChatPage::instance()->showNotification(
tr("Failed to delete old image pack: %1")
.arg(QString::fromStdString(e->matrix_error.error)));
});
}
http::client()->send_state_event(
roomid_,
statekey_,
pack,
[this](const mtx::responses::EventId &, mtx::http::RequestErr e) {
if (e)
ChatPage::instance()->showNotification(
tr("Failed to update image pack: %1")
.arg(QString::fromStdString(e->matrix_error.error)));
nhlog::net()->info("Uploaded image pack: %1", statekey_);
});
if (roomid_.empty()) {
http::client()->put_account_data(pack, [](mtx::http::RequestErr e) {
if (e)
ChatPage::instance()->showNotification(
tr("Failed to update image pack: %1")
.arg(QString::fromStdString(e->matrix_error.error)));
});
} else {
if (old_statekey_ != statekey_) {
http::client()->send_state_event(
roomid_,
to_string(mtx::events::EventType::ImagePackInRoom),
old_statekey_,
nlohmann::json::object(),
[](const mtx::responses::EventId &, mtx::http::RequestErr e) {
if (e)
ChatPage::instance()->showNotification(
tr("Failed to delete old image pack: %1")
.arg(QString::fromStdString(e->matrix_error.error)));
});
}
http::client()->send_state_event(
roomid_,
statekey_,
pack,
[this](const mtx::responses::EventId &, mtx::http::RequestErr e) {
if (e)
ChatPage::instance()->showNotification(
tr("Failed to update image pack: %1")
.arg(QString::fromStdString(e->matrix_error.error)));
nhlog::net()->info("Uploaded image pack: %1", statekey_);
});
}
}
void
SingleImagePackModel::addStickers(QList<QUrl> files)
{
for (const auto &f : files) {
auto file = QFile(f.toLocalFile());
if (!file.open(QFile::ReadOnly)) {
ChatPage::instance()->showNotification(
tr("Failed to open image: %1").arg(f.toLocalFile()));
return;
}
auto bytes = file.readAll();
auto img = utils::readImage(bytes);
mtx::common::ImageInfo info{};
auto sz = img.size() / 2;
if (sz.width() > 512 || sz.height() > 512) {
sz.scale(512, 512, Qt::AspectRatioMode::KeepAspectRatio);
} else if (img.height() < 128 && img.width() < 128) {
sz = img.size();
}
info.h = sz.height();
info.w = sz.width();
info.size = bytes.size();
info.mimetype =
QMimeDatabase().mimeTypeForFile(f.toLocalFile()).name().toStdString();
auto filename = f.fileName().toStdString();
http::client()->upload(
bytes.toStdString(),
QMimeDatabase().mimeTypeForFile(f.toLocalFile()).name().toStdString(),
filename,
[this, filename, info](const mtx::responses::ContentURI &uri,
mtx::http::RequestErr e) {
if (e) {
ChatPage::instance()->showNotification(
tr("Failed to upload image: %1")
.arg(QString::fromStdString(e->matrix_error.error)));
return;
}
emit addImage(uri.content_uri, filename, info);
});
for (const auto &f : files) {
auto file = QFile(f.toLocalFile());
if (!file.open(QFile::ReadOnly)) {
ChatPage::instance()->showNotification(
tr("Failed to open image: %1").arg(f.toLocalFile()));
return;
}
auto bytes = file.readAll();
auto img = utils::readImage(bytes);
mtx::common::ImageInfo info{};
auto sz = img.size() / 2;
if (sz.width() > 512 || sz.height() > 512) {
sz.scale(512, 512, Qt::AspectRatioMode::KeepAspectRatio);
} else if (img.height() < 128 && img.width() < 128) {
sz = img.size();
}
info.h = sz.height();
info.w = sz.width();
info.size = bytes.size();
info.mimetype = QMimeDatabase().mimeTypeForFile(f.toLocalFile()).name().toStdString();
auto filename = f.fileName().toStdString();
http::client()->upload(
bytes.toStdString(),
QMimeDatabase().mimeTypeForFile(f.toLocalFile()).name().toStdString(),
filename,
[this, filename, info](const mtx::responses::ContentURI &uri, mtx::http::RequestErr e) {
if (e) {
ChatPage::instance()->showNotification(
tr("Failed to upload image: %1")
.arg(QString::fromStdString(e->matrix_error.error)));
return;
}
emit addImage(uri.content_uri, filename, info);
});
}
}
void
SingleImagePackModel::remove(int idx)
{
if (idx < (int)shortcodes.size() && idx >= 0) {
beginRemoveRows(QModelIndex(), idx, idx);
auto s = shortcodes.at(idx);
shortcodes.erase(shortcodes.begin() + idx);
pack.images.erase(s);
endRemoveRows();
}
if (idx < (int)shortcodes.size() && idx >= 0) {
beginRemoveRows(QModelIndex(), idx, idx);
auto s = shortcodes.at(idx);
shortcodes.erase(shortcodes.begin() + idx);
pack.images.erase(s);
endRemoveRows();
}
}
void
SingleImagePackModel::addImageCb(std::string uri, std::string filename, mtx::common::ImageInfo info)
{
mtx::events::msc2545::PackImage img{};
img.url = uri;
img.info = info;
beginInsertRows(
QModelIndex(), static_cast<int>(shortcodes.size()), static_cast<int>(shortcodes.size()));
mtx::events::msc2545::PackImage img{};
img.url = uri;
img.info = info;
beginInsertRows(
QModelIndex(), static_cast<int>(shortcodes.size()), static_cast<int>(shortcodes.size()));
pack.images[filename] = img;
shortcodes.push_back(filename);
pack.images[filename] = img;
shortcodes.push_back(filename);
endInsertRows();
endInsertRows();
}

@ -14,81 +14,78 @@
class SingleImagePackModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QString roomid READ roomid CONSTANT)
Q_PROPERTY(QString statekey READ statekey WRITE setStatekey NOTIFY statekeyChanged)
Q_PROPERTY(
QString attribution READ attribution WRITE setAttribution NOTIFY attributionChanged)
Q_PROPERTY(QString packname READ packname WRITE setPackname NOTIFY packnameChanged)
Q_PROPERTY(QString avatarUrl READ avatarUrl WRITE setAvatarUrl NOTIFY avatarUrlChanged)
Q_PROPERTY(
bool isStickerPack READ isStickerPack WRITE setIsStickerPack NOTIFY isStickerPackChanged)
Q_PROPERTY(bool isEmotePack READ isEmotePack WRITE setIsEmotePack NOTIFY isEmotePackChanged)
Q_PROPERTY(bool isGloballyEnabled READ isGloballyEnabled WRITE setGloballyEnabled NOTIFY
globallyEnabledChanged)
Q_PROPERTY(bool canEdit READ canEdit CONSTANT)
Q_OBJECT
Q_PROPERTY(QString roomid READ roomid CONSTANT)
Q_PROPERTY(QString statekey READ statekey WRITE setStatekey NOTIFY statekeyChanged)
Q_PROPERTY(QString attribution READ attribution WRITE setAttribution NOTIFY attributionChanged)
Q_PROPERTY(QString packname READ packname WRITE setPackname NOTIFY packnameChanged)
Q_PROPERTY(QString avatarUrl READ avatarUrl WRITE setAvatarUrl NOTIFY avatarUrlChanged)
Q_PROPERTY(
bool isStickerPack READ isStickerPack WRITE setIsStickerPack NOTIFY isStickerPackChanged)
Q_PROPERTY(bool isEmotePack READ isEmotePack WRITE setIsEmotePack NOTIFY isEmotePackChanged)
Q_PROPERTY(bool isGloballyEnabled READ isGloballyEnabled WRITE setGloballyEnabled NOTIFY
globallyEnabledChanged)
Q_PROPERTY(bool canEdit READ canEdit CONSTANT)
public:
enum Roles
{
Url = Qt::UserRole,
ShortCode,
Body,
IsEmote,
IsSticker,
};
Q_ENUM(Roles);
SingleImagePackModel(ImagePackInfo pack_, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
bool setData(const QModelIndex &index,
const QVariant &value,
int role = Qt::EditRole) override;
QString roomid() const { return QString::fromStdString(roomid_); }
QString statekey() const { return QString::fromStdString(statekey_); }
QString packname() const { return QString::fromStdString(pack.pack->display_name); }
QString attribution() const { return QString::fromStdString(pack.pack->attribution); }
QString avatarUrl() const { return QString::fromStdString(pack.pack->avatar_url); }
bool isStickerPack() const { return pack.pack->is_sticker(); }
bool isEmotePack() const { return pack.pack->is_emoji(); }
bool isGloballyEnabled() const;
bool canEdit() const;
void setGloballyEnabled(bool enabled);
void setPackname(QString val);
void setAttribution(QString val);
void setAvatarUrl(QString val);
void setStatekey(QString val);
void setIsStickerPack(bool val);
void setIsEmotePack(bool val);
Q_INVOKABLE void save();
Q_INVOKABLE void addStickers(QList<QUrl> files);
Q_INVOKABLE void remove(int index);
enum Roles
{
Url = Qt::UserRole,
ShortCode,
Body,
IsEmote,
IsSticker,
};
Q_ENUM(Roles);
SingleImagePackModel(ImagePackInfo pack_, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
QString roomid() const { return QString::fromStdString(roomid_); }
QString statekey() const { return QString::fromStdString(statekey_); }
QString packname() const { return QString::fromStdString(pack.pack->display_name); }
QString attribution() const { return QString::fromStdString(pack.pack->attribution); }
QString avatarUrl() const { return QString::fromStdString(pack.pack->avatar_url); }
bool isStickerPack() const { return pack.pack->is_sticker(); }
bool isEmotePack() const { return pack.pack->is_emoji(); }
bool isGloballyEnabled() const;
bool canEdit() const;
void setGloballyEnabled(bool enabled);
void setPackname(QString val);
void setAttribution(QString val);
void setAvatarUrl(QString val);
void setStatekey(QString val);
void setIsStickerPack(bool val);
void setIsEmotePack(bool val);
Q_INVOKABLE void save();
Q_INVOKABLE void addStickers(QList<QUrl> files);
Q_INVOKABLE void remove(int index);
signals:
void globallyEnabledChanged();
void statekeyChanged();
void attributionChanged();
void packnameChanged();
void avatarUrlChanged();
void isEmotePackChanged();
void isStickerPackChanged();
void globallyEnabledChanged();
void statekeyChanged();
void attributionChanged();
void packnameChanged();
void avatarUrlChanged();
void isEmotePackChanged();
void isStickerPackChanged();
void addImage(std::string uri, std::string filename, mtx::common::ImageInfo info);
void addImage(std::string uri, std::string filename, mtx::common::ImageInfo info);
private slots:
void addImageCb(std::string uri, std::string filename, mtx::common::ImageInfo info);
void addImageCb(std::string uri, std::string filename, mtx::common::ImageInfo info);
private:
std::string roomid_;
std::string statekey_, old_statekey_;
std::string roomid_;
std::string statekey_, old_statekey_;
mtx::events::msc2545::ImagePack pack;
std::vector<std::string> shortcodes;
mtx::events::msc2545::ImagePack pack;
std::vector<std::string> shortcodes;
};

@ -19,7 +19,7 @@
MsgCountComposedIcon::MsgCountComposedIcon(const QString &filename)
: QIconEngine()
{
icon_ = QIcon(filename);
icon_ = QIcon(filename);
}
void
@ -28,95 +28,95 @@ MsgCountComposedIcon::paint(QPainter *painter,
QIcon::Mode mode,
QIcon::State state)
{
painter->setRenderHint(QPainter::TextAntialiasing);
painter->setRenderHint(QPainter::SmoothPixmapTransform);
painter->setRenderHint(QPainter::Antialiasing);
icon_.paint(painter, rect, Qt::AlignCenter, mode, state);
if (msgCount <= 0)
return;
QColor backgroundColor("red");
QColor textColor("white");
QBrush brush;
brush.setStyle(Qt::SolidPattern);
brush.setColor(backgroundColor);
QFont f;
f.setPointSizeF(8);
f.setWeight(QFont::Thin);
painter->setBrush(brush);
painter->setPen(Qt::NoPen);
painter->setFont(f);
QRectF bubble(rect.width() - BubbleDiameter,
rect.height() - BubbleDiameter,
BubbleDiameter,
BubbleDiameter);
painter->drawEllipse(bubble);
painter->setPen(QPen(textColor));
painter->setBrush(Qt::NoBrush);
painter->drawText(bubble, Qt::AlignCenter, QString::number(msgCount));
painter->setRenderHint(QPainter::TextAntialiasing);
painter->setRenderHint(QPainter::SmoothPixmapTransform);
painter->setRenderHint(QPainter::Antialiasing);
icon_.paint(painter, rect, Qt::AlignCenter, mode, state);
if (msgCount <= 0)
return;
QColor backgroundColor("red");
QColor textColor("white");
QBrush brush;
brush.setStyle(Qt::SolidPattern);
brush.setColor(backgroundColor);
QFont f;
f.setPointSizeF(8);
f.setWeight(QFont::Thin);
painter->setBrush(brush);
painter->setPen(Qt::NoPen);
painter->setFont(f);
QRectF bubble(rect.width() - BubbleDiameter,
rect.height() - BubbleDiameter,
BubbleDiameter,
BubbleDiameter);
painter->drawEllipse(bubble);
painter->setPen(QPen(textColor));
painter->setBrush(Qt::NoBrush);
painter->drawText(bubble, Qt::AlignCenter, QString::number(msgCount));
}
QIconEngine *
MsgCountComposedIcon::clone() const
{
return new MsgCountComposedIcon(*this);
return new MsgCountComposedIcon(*this);
}
QList<QSize>
MsgCountComposedIcon::availableSizes(QIcon::Mode mode, QIcon::State state) const
{
Q_UNUSED(mode);
Q_UNUSED(state);
QList<QSize> sizes;
sizes.append(QSize(24, 24));
sizes.append(QSize(32, 32));
sizes.append(QSize(48, 48));
sizes.append(QSize(64, 64));
sizes.append(QSize(128, 128));
sizes.append(QSize(256, 256));
return sizes;
Q_UNUSED(mode);
Q_UNUSED(state);
QList<QSize> sizes;
sizes.append(QSize(24, 24));
sizes.append(QSize(32, 32));
sizes.append(QSize(48, 48));
sizes.append(QSize(64, 64));
sizes.append(QSize(128, 128));
sizes.append(QSize(256, 256));
return sizes;
}
QPixmap
MsgCountComposedIcon::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)
{
QImage img(size, QImage::Format_ARGB32);
img.fill(qRgba(0, 0, 0, 0));
QPixmap result = QPixmap::fromImage(img, Qt::NoFormatConversion);
{
QPainter painter(&result);
paint(&painter, QRect(QPoint(0, 0), size), mode, state);
}
return result;
QImage img(size, QImage::Format_ARGB32);
img.fill(qRgba(0, 0, 0, 0));
QPixmap result = QPixmap::fromImage(img, Qt::NoFormatConversion);
{
QPainter painter(&result);
paint(&painter, QRect(QPoint(0, 0), size), mode, state);
}
return result;
}
TrayIcon::TrayIcon(const QString &filename, QWidget *parent)
: QSystemTrayIcon(parent)
{
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
setIcon(QIcon(filename));
setIcon(QIcon(filename));
#else
icon_ = new MsgCountComposedIcon(filename);
setIcon(QIcon(icon_));
icon_ = new MsgCountComposedIcon(filename);
setIcon(QIcon(icon_));
#endif
QMenu *menu = new QMenu(parent);
setContextMenu(menu);
QMenu *menu = new QMenu(parent);
setContextMenu(menu);
viewAction_ = new QAction(tr("Show"), this);
quitAction_ = new QAction(tr("Quit"), this);
viewAction_ = new QAction(tr("Show"), this);
quitAction_ = new QAction(tr("Quit"), this);
connect(viewAction_, SIGNAL(triggered()), parent, SLOT(show()));
connect(quitAction_, &QAction::triggered, this, QApplication::quit);
connect(viewAction_, SIGNAL(triggered()), parent, SLOT(show()));
connect(quitAction_, &QAction::triggered, this, QApplication::quit);
menu->addAction(viewAction_);
menu->addAction(quitAction_);
menu->addAction(viewAction_);
menu->addAction(quitAction_);
}
void
@ -127,25 +127,25 @@ TrayIcon::setUnreadCount(int count)
// currently, to avoid writing obj-c code, ignore deprecated warnings on the badge functions
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
auto labelText = count == 0 ? "" : QString::number(count);
auto labelText = count == 0 ? "" : QString::number(count);
if (labelText == QtMac::badgeLabelText())
return;
if (labelText == QtMac::badgeLabelText())
return;
QtMac::setBadgeLabelText(labelText);
QtMac::setBadgeLabelText(labelText);
#pragma clang diagnostic pop
#elif defined(Q_OS_WIN)
// FIXME: Find a way to use Windows apis for the badge counter (if any).
#else
if (count == icon_->msgCount)
return;
if (count == icon_->msgCount)
return;
// Custom drawing on Linux.
MsgCountComposedIcon *tmp = static_cast<MsgCountComposedIcon *>(icon_->clone());
tmp->msgCount = count;
// Custom drawing on Linux.
MsgCountComposedIcon *tmp = static_cast<MsgCountComposedIcon *>(icon_->clone());
tmp->msgCount = count;
setIcon(QIcon(tmp));
setIcon(QIcon(tmp));
icon_ = tmp;
icon_ = tmp;
#endif
}

@ -16,33 +16,33 @@ class QPainter;
class MsgCountComposedIcon : public QIconEngine
{
public:
MsgCountComposedIcon(const QString &filename);
MsgCountComposedIcon(const QString &filename);
void paint(QPainter *p, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
QIconEngine *clone() const override;
QList<QSize> availableSizes(QIcon::Mode mode, QIcon::State state) const override;
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
void paint(QPainter *p, const QRect &rect, QIcon::Mode mode, QIcon::State state) override;
QIconEngine *clone() const override;
QList<QSize> availableSizes(QIcon::Mode mode, QIcon::State state) const override;
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override;
int msgCount = 0;
int msgCount = 0;
private:
const int BubbleDiameter = 17;
const int BubbleDiameter = 17;
QIcon icon_;
QIcon icon_;
};
class TrayIcon : public QSystemTrayIcon
{
Q_OBJECT
Q_OBJECT
public:
TrayIcon(const QString &filename, QWidget *parent);
TrayIcon(const QString &filename, QWidget *parent);
public slots:
void setUnreadCount(int count);
void setUnreadCount(int count);
private:
QAction *viewAction_;
QAction *quitAction_;
QAction *viewAction_;
QAction *quitAction_;
MsgCountComposedIcon *icon_;
MsgCountComposedIcon *icon_;
};

File diff suppressed because it is too large Load Diff

@ -30,402 +30,395 @@ constexpr int LayoutBottomMargin = LayoutTopMargin;
class UserSettings : public QObject
{
Q_OBJECT
Q_PROPERTY(QString theme READ theme WRITE setTheme NOTIFY themeChanged)
Q_PROPERTY(bool messageHoverHighlight READ messageHoverHighlight WRITE
setMessageHoverHighlight NOTIFY messageHoverHighlightChanged)
Q_PROPERTY(bool enlargeEmojiOnlyMessages READ enlargeEmojiOnlyMessages WRITE
setEnlargeEmojiOnlyMessages NOTIFY enlargeEmojiOnlyMessagesChanged)
Q_PROPERTY(bool tray READ tray WRITE setTray NOTIFY trayChanged)
Q_PROPERTY(bool startInTray READ startInTray WRITE setStartInTray NOTIFY startInTrayChanged)
Q_PROPERTY(bool groupView READ groupView WRITE setGroupView NOTIFY groupViewStateChanged)
Q_PROPERTY(bool markdown READ markdown WRITE setMarkdown NOTIFY markdownChanged)
Q_PROPERTY(bool animateImagesOnHover READ animateImagesOnHover WRITE setAnimateImagesOnHover
NOTIFY animateImagesOnHoverChanged)
Q_PROPERTY(bool typingNotifications READ typingNotifications WRITE setTypingNotifications
NOTIFY typingNotificationsChanged)
Q_PROPERTY(bool sortByImportance READ sortByImportance WRITE setSortByImportance NOTIFY
roomSortingChanged)
Q_PROPERTY(bool buttonsInTimeline READ buttonsInTimeline WRITE setButtonsInTimeline NOTIFY
buttonInTimelineChanged)
Q_PROPERTY(
bool readReceipts READ readReceipts WRITE setReadReceipts NOTIFY readReceiptsChanged)
Q_PROPERTY(bool desktopNotifications READ hasDesktopNotifications WRITE
setDesktopNotifications NOTIFY desktopNotificationsChanged)
Q_PROPERTY(bool alertOnNotification READ hasAlertOnNotification WRITE setAlertOnNotification
NOTIFY alertOnNotificationChanged)
Q_PROPERTY(
bool avatarCircles READ avatarCircles WRITE setAvatarCircles NOTIFY avatarCirclesChanged)
Q_PROPERTY(bool decryptSidebar READ decryptSidebar WRITE setDecryptSidebar NOTIFY
decryptSidebarChanged)
Q_PROPERTY(
bool privacyScreen READ privacyScreen WRITE setPrivacyScreen NOTIFY privacyScreenChanged)
Q_PROPERTY(int privacyScreenTimeout READ privacyScreenTimeout WRITE setPrivacyScreenTimeout
NOTIFY privacyScreenTimeoutChanged)
Q_PROPERTY(int timelineMaxWidth READ timelineMaxWidth WRITE setTimelineMaxWidth NOTIFY
timelineMaxWidthChanged)
Q_PROPERTY(
int roomListWidth READ roomListWidth WRITE setRoomListWidth NOTIFY roomListWidthChanged)
Q_PROPERTY(int communityListWidth READ communityListWidth WRITE setCommunityListWidth NOTIFY
communityListWidthChanged)
Q_PROPERTY(bool mobileMode READ mobileMode WRITE setMobileMode NOTIFY mobileModeChanged)
Q_PROPERTY(double fontSize READ fontSize WRITE setFontSize NOTIFY fontSizeChanged)
Q_PROPERTY(QString font READ font WRITE setFontFamily NOTIFY fontChanged)
Q_PROPERTY(
QString emojiFont READ emojiFont WRITE setEmojiFontFamily NOTIFY emojiFontChanged)
Q_PROPERTY(Presence presence READ presence WRITE setPresence NOTIFY presenceChanged)
Q_PROPERTY(QString ringtone READ ringtone WRITE setRingtone NOTIFY ringtoneChanged)
Q_PROPERTY(QString microphone READ microphone WRITE setMicrophone NOTIFY microphoneChanged)
Q_PROPERTY(QString camera READ camera WRITE setCamera NOTIFY cameraChanged)
Q_PROPERTY(QString cameraResolution READ cameraResolution WRITE setCameraResolution NOTIFY
cameraResolutionChanged)
Q_PROPERTY(QString cameraFrameRate READ cameraFrameRate WRITE setCameraFrameRate NOTIFY
cameraFrameRateChanged)
Q_PROPERTY(int screenShareFrameRate READ screenShareFrameRate WRITE setScreenShareFrameRate
NOTIFY screenShareFrameRateChanged)
Q_PROPERTY(bool screenSharePiP READ screenSharePiP WRITE setScreenSharePiP NOTIFY
screenSharePiPChanged)
Q_PROPERTY(bool screenShareRemoteVideo READ screenShareRemoteVideo WRITE
setScreenShareRemoteVideo NOTIFY screenShareRemoteVideoChanged)
Q_PROPERTY(bool screenShareHideCursor READ screenShareHideCursor WRITE
setScreenShareHideCursor NOTIFY screenShareHideCursorChanged)
Q_PROPERTY(
bool useStunServer READ useStunServer WRITE setUseStunServer NOTIFY useStunServerChanged)
Q_PROPERTY(bool onlyShareKeysWithVerifiedUsers READ onlyShareKeysWithVerifiedUsers WRITE
setOnlyShareKeysWithVerifiedUsers NOTIFY onlyShareKeysWithVerifiedUsersChanged)
Q_PROPERTY(bool shareKeysWithTrustedUsers READ shareKeysWithTrustedUsers WRITE
setShareKeysWithTrustedUsers NOTIFY shareKeysWithTrustedUsersChanged)
Q_PROPERTY(bool useOnlineKeyBackup READ useOnlineKeyBackup WRITE setUseOnlineKeyBackup
NOTIFY useOnlineKeyBackupChanged)
Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged)
Q_PROPERTY(QString userId READ userId WRITE setUserId NOTIFY userIdChanged)
Q_PROPERTY(
QString accessToken READ accessToken WRITE setAccessToken NOTIFY accessTokenChanged)
Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged)
Q_PROPERTY(QString homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged)
Q_PROPERTY(bool disableCertificateValidation READ disableCertificateValidation WRITE
setDisableCertificateValidation NOTIFY disableCertificateValidationChanged)
Q_PROPERTY(
bool useIdenticon READ useIdenticon WRITE setUseIdenticon NOTIFY useIdenticonChanged)
UserSettings();
Q_OBJECT
Q_PROPERTY(QString theme READ theme WRITE setTheme NOTIFY themeChanged)
Q_PROPERTY(bool messageHoverHighlight READ messageHoverHighlight WRITE setMessageHoverHighlight
NOTIFY messageHoverHighlightChanged)
Q_PROPERTY(bool enlargeEmojiOnlyMessages READ enlargeEmojiOnlyMessages WRITE
setEnlargeEmojiOnlyMessages NOTIFY enlargeEmojiOnlyMessagesChanged)
Q_PROPERTY(bool tray READ tray WRITE setTray NOTIFY trayChanged)
Q_PROPERTY(bool startInTray READ startInTray WRITE setStartInTray NOTIFY startInTrayChanged)
Q_PROPERTY(bool groupView READ groupView WRITE setGroupView NOTIFY groupViewStateChanged)
Q_PROPERTY(bool markdown READ markdown WRITE setMarkdown NOTIFY markdownChanged)
Q_PROPERTY(bool animateImagesOnHover READ animateImagesOnHover WRITE setAnimateImagesOnHover
NOTIFY animateImagesOnHoverChanged)
Q_PROPERTY(bool typingNotifications READ typingNotifications WRITE setTypingNotifications NOTIFY
typingNotificationsChanged)
Q_PROPERTY(bool sortByImportance READ sortByImportance WRITE setSortByImportance NOTIFY
roomSortingChanged)
Q_PROPERTY(bool buttonsInTimeline READ buttonsInTimeline WRITE setButtonsInTimeline NOTIFY
buttonInTimelineChanged)
Q_PROPERTY(bool readReceipts READ readReceipts WRITE setReadReceipts NOTIFY readReceiptsChanged)
Q_PROPERTY(bool desktopNotifications READ hasDesktopNotifications WRITE setDesktopNotifications
NOTIFY desktopNotificationsChanged)
Q_PROPERTY(bool alertOnNotification READ hasAlertOnNotification WRITE setAlertOnNotification
NOTIFY alertOnNotificationChanged)
Q_PROPERTY(
bool avatarCircles READ avatarCircles WRITE setAvatarCircles NOTIFY avatarCirclesChanged)
Q_PROPERTY(
bool decryptSidebar READ decryptSidebar WRITE setDecryptSidebar NOTIFY decryptSidebarChanged)
Q_PROPERTY(
bool privacyScreen READ privacyScreen WRITE setPrivacyScreen NOTIFY privacyScreenChanged)
Q_PROPERTY(int privacyScreenTimeout READ privacyScreenTimeout WRITE setPrivacyScreenTimeout
NOTIFY privacyScreenTimeoutChanged)
Q_PROPERTY(int timelineMaxWidth READ timelineMaxWidth WRITE setTimelineMaxWidth NOTIFY
timelineMaxWidthChanged)
Q_PROPERTY(
int roomListWidth READ roomListWidth WRITE setRoomListWidth NOTIFY roomListWidthChanged)
Q_PROPERTY(int communityListWidth READ communityListWidth WRITE setCommunityListWidth NOTIFY
communityListWidthChanged)
Q_PROPERTY(bool mobileMode READ mobileMode WRITE setMobileMode NOTIFY mobileModeChanged)
Q_PROPERTY(double fontSize READ fontSize WRITE setFontSize NOTIFY fontSizeChanged)
Q_PROPERTY(QString font READ font WRITE setFontFamily NOTIFY fontChanged)
Q_PROPERTY(QString emojiFont READ emojiFont WRITE setEmojiFontFamily NOTIFY emojiFontChanged)
Q_PROPERTY(Presence presence READ presence WRITE setPresence NOTIFY presenceChanged)
Q_PROPERTY(QString ringtone READ ringtone WRITE setRingtone NOTIFY ringtoneChanged)
Q_PROPERTY(QString microphone READ microphone WRITE setMicrophone NOTIFY microphoneChanged)
Q_PROPERTY(QString camera READ camera WRITE setCamera NOTIFY cameraChanged)
Q_PROPERTY(QString cameraResolution READ cameraResolution WRITE setCameraResolution NOTIFY
cameraResolutionChanged)
Q_PROPERTY(QString cameraFrameRate READ cameraFrameRate WRITE setCameraFrameRate NOTIFY
cameraFrameRateChanged)
Q_PROPERTY(int screenShareFrameRate READ screenShareFrameRate WRITE setScreenShareFrameRate
NOTIFY screenShareFrameRateChanged)
Q_PROPERTY(
bool screenSharePiP READ screenSharePiP WRITE setScreenSharePiP NOTIFY screenSharePiPChanged)
Q_PROPERTY(bool screenShareRemoteVideo READ screenShareRemoteVideo WRITE
setScreenShareRemoteVideo NOTIFY screenShareRemoteVideoChanged)
Q_PROPERTY(bool screenShareHideCursor READ screenShareHideCursor WRITE setScreenShareHideCursor
NOTIFY screenShareHideCursorChanged)
Q_PROPERTY(
bool useStunServer READ useStunServer WRITE setUseStunServer NOTIFY useStunServerChanged)
Q_PROPERTY(bool onlyShareKeysWithVerifiedUsers READ onlyShareKeysWithVerifiedUsers WRITE
setOnlyShareKeysWithVerifiedUsers NOTIFY onlyShareKeysWithVerifiedUsersChanged)
Q_PROPERTY(bool shareKeysWithTrustedUsers READ shareKeysWithTrustedUsers WRITE
setShareKeysWithTrustedUsers NOTIFY shareKeysWithTrustedUsersChanged)
Q_PROPERTY(bool useOnlineKeyBackup READ useOnlineKeyBackup WRITE setUseOnlineKeyBackup NOTIFY
useOnlineKeyBackupChanged)
Q_PROPERTY(QString profile READ profile WRITE setProfile NOTIFY profileChanged)
Q_PROPERTY(QString userId READ userId WRITE setUserId NOTIFY userIdChanged)
Q_PROPERTY(QString accessToken READ accessToken WRITE setAccessToken NOTIFY accessTokenChanged)
Q_PROPERTY(QString deviceId READ deviceId WRITE setDeviceId NOTIFY deviceIdChanged)
Q_PROPERTY(QString homeserver READ homeserver WRITE setHomeserver NOTIFY homeserverChanged)
Q_PROPERTY(bool disableCertificateValidation READ disableCertificateValidation WRITE
setDisableCertificateValidation NOTIFY disableCertificateValidationChanged)
Q_PROPERTY(bool useIdenticon READ useIdenticon WRITE setUseIdenticon NOTIFY useIdenticonChanged)
UserSettings();
public:
static QSharedPointer<UserSettings> instance();
static void initialize(std::optional<QString> profile);
QSettings *qsettings() { return &settings; }
enum class Presence
{
AutomaticPresence,
Online,
Unavailable,
Offline,
};
Q_ENUM(Presence)
void save();
void load(std::optional<QString> profile);
void applyTheme();
void setTheme(QString theme);
void setMessageHoverHighlight(bool state);
void setEnlargeEmojiOnlyMessages(bool state);
void setTray(bool state);
void setStartInTray(bool state);
void setMobileMode(bool mode);
void setFontSize(double size);
void setFontFamily(QString family);
void setEmojiFontFamily(QString family);
void setGroupView(bool state);
void setMarkdown(bool state);
void setAnimateImagesOnHover(bool state);
void setReadReceipts(bool state);
void setTypingNotifications(bool state);
void setSortByImportance(bool state);
void setButtonsInTimeline(bool state);
void setTimelineMaxWidth(int state);
void setCommunityListWidth(int state);
void setRoomListWidth(int state);
void setDesktopNotifications(bool state);
void setAlertOnNotification(bool state);
void setAvatarCircles(bool state);
void setDecryptSidebar(bool state);
void setPrivacyScreen(bool state);
void setPrivacyScreenTimeout(int state);
void setPresence(Presence state);
void setRingtone(QString ringtone);
void setMicrophone(QString microphone);
void setCamera(QString camera);
void setCameraResolution(QString resolution);
void setCameraFrameRate(QString frameRate);
void setScreenShareFrameRate(int frameRate);
void setScreenSharePiP(bool state);
void setScreenShareRemoteVideo(bool state);
void setScreenShareHideCursor(bool state);
void setUseStunServer(bool state);
void setOnlyShareKeysWithVerifiedUsers(bool state);
void setShareKeysWithTrustedUsers(bool state);
void setUseOnlineKeyBackup(bool state);
void setProfile(QString profile);
void setUserId(QString userId);
void setAccessToken(QString accessToken);
void setDeviceId(QString deviceId);
void setHomeserver(QString homeserver);
void setDisableCertificateValidation(bool disabled);
void setHiddenTags(QStringList hiddenTags);
void setUseIdenticon(bool state);
QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; }
bool messageHoverHighlight() const { return messageHoverHighlight_; }
bool enlargeEmojiOnlyMessages() const { return enlargeEmojiOnlyMessages_; }
bool tray() const { return tray_; }
bool startInTray() const { return startInTray_; }
bool groupView() const { return groupView_; }
bool avatarCircles() const { return avatarCircles_; }
bool decryptSidebar() const { return decryptSidebar_; }
bool privacyScreen() const { return privacyScreen_; }
int privacyScreenTimeout() const { return privacyScreenTimeout_; }
bool markdown() const { return markdown_; }
bool animateImagesOnHover() const { return animateImagesOnHover_; }
bool typingNotifications() const { return typingNotifications_; }
bool sortByImportance() const { return sortByImportance_; }
bool buttonsInTimeline() const { return buttonsInTimeline_; }
bool mobileMode() const { return mobileMode_; }
bool readReceipts() const { return readReceipts_; }
bool hasDesktopNotifications() const { return hasDesktopNotifications_; }
bool hasAlertOnNotification() const { return hasAlertOnNotification_; }
bool hasNotifications() const
{
return hasDesktopNotifications() || hasAlertOnNotification();
static QSharedPointer<UserSettings> instance();
static void initialize(std::optional<QString> profile);
QSettings *qsettings() { return &settings; }
enum class Presence
{
AutomaticPresence,
Online,
Unavailable,
Offline,
};
Q_ENUM(Presence)
void save();
void load(std::optional<QString> profile);
void applyTheme();
void setTheme(QString theme);
void setMessageHoverHighlight(bool state);
void setEnlargeEmojiOnlyMessages(bool state);
void setTray(bool state);
void setStartInTray(bool state);
void setMobileMode(bool mode);
void setFontSize(double size);
void setFontFamily(QString family);
void setEmojiFontFamily(QString family);
void setGroupView(bool state);
void setMarkdown(bool state);
void setAnimateImagesOnHover(bool state);
void setReadReceipts(bool state);
void setTypingNotifications(bool state);
void setSortByImportance(bool state);
void setButtonsInTimeline(bool state);
void setTimelineMaxWidth(int state);
void setCommunityListWidth(int state);
void setRoomListWidth(int state);
void setDesktopNotifications(bool state);
void setAlertOnNotification(bool state);
void setAvatarCircles(bool state);
void setDecryptSidebar(bool state);
void setPrivacyScreen(bool state);
void setPrivacyScreenTimeout(int state);
void setPresence(Presence state);
void setRingtone(QString ringtone);
void setMicrophone(QString microphone);
void setCamera(QString camera);
void setCameraResolution(QString resolution);
void setCameraFrameRate(QString frameRate);
void setScreenShareFrameRate(int frameRate);
void setScreenSharePiP(bool state);
void setScreenShareRemoteVideo(bool state);
void setScreenShareHideCursor(bool state);
void setUseStunServer(bool state);
void setOnlyShareKeysWithVerifiedUsers(bool state);
void setShareKeysWithTrustedUsers(bool state);
void setUseOnlineKeyBackup(bool state);
void setProfile(QString profile);
void setUserId(QString userId);
void setAccessToken(QString accessToken);
void setDeviceId(QString deviceId);
void setHomeserver(QString homeserver);
void setDisableCertificateValidation(bool disabled);
void setHiddenTags(QStringList hiddenTags);
void setUseIdenticon(bool state);
QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; }
bool messageHoverHighlight() const { return messageHoverHighlight_; }
bool enlargeEmojiOnlyMessages() const { return enlargeEmojiOnlyMessages_; }
bool tray() const { return tray_; }
bool startInTray() const { return startInTray_; }
bool groupView() const { return groupView_; }
bool avatarCircles() const { return avatarCircles_; }
bool decryptSidebar() const { return decryptSidebar_; }
bool privacyScreen() const { return privacyScreen_; }
int privacyScreenTimeout() const { return privacyScreenTimeout_; }
bool markdown() const { return markdown_; }
bool animateImagesOnHover() const { return animateImagesOnHover_; }
bool typingNotifications() const { return typingNotifications_; }
bool sortByImportance() const { return sortByImportance_; }
bool buttonsInTimeline() const { return buttonsInTimeline_; }
bool mobileMode() const { return mobileMode_; }
bool readReceipts() const { return readReceipts_; }
bool hasDesktopNotifications() const { return hasDesktopNotifications_; }
bool hasAlertOnNotification() const { return hasAlertOnNotification_; }
bool hasNotifications() const { return hasDesktopNotifications() || hasAlertOnNotification(); }
int timelineMaxWidth() const { return timelineMaxWidth_; }
int communityListWidth() const { return communityListWidth_; }
int roomListWidth() const { return roomListWidth_; }
double fontSize() const { return baseFontSize_; }
QString font() const { return font_; }
QString emojiFont() const
{
if (emojiFont_ == "Default") {
return tr("Default");
}
int timelineMaxWidth() const { return timelineMaxWidth_; }
int communityListWidth() const { return communityListWidth_; }
int roomListWidth() const { return roomListWidth_; }
double fontSize() const { return baseFontSize_; }
QString font() const { return font_; }
QString emojiFont() const
{
if (emojiFont_ == "Default") {
return tr("Default");
}
return emojiFont_;
}
Presence presence() const { return presence_; }
QString ringtone() const { return ringtone_; }
QString microphone() const { return microphone_; }
QString camera() const { return camera_; }
QString cameraResolution() const { return cameraResolution_; }
QString cameraFrameRate() const { return cameraFrameRate_; }
int screenShareFrameRate() const { return screenShareFrameRate_; }
bool screenSharePiP() const { return screenSharePiP_; }
bool screenShareRemoteVideo() const { return screenShareRemoteVideo_; }
bool screenShareHideCursor() const { return screenShareHideCursor_; }
bool useStunServer() const { return useStunServer_; }
bool shareKeysWithTrustedUsers() const { return shareKeysWithTrustedUsers_; }
bool onlyShareKeysWithVerifiedUsers() const { return onlyShareKeysWithVerifiedUsers_; }
bool useOnlineKeyBackup() const { return useOnlineKeyBackup_; }
QString profile() const { return profile_; }
QString userId() const { return userId_; }
QString accessToken() const { return accessToken_; }
QString deviceId() const { return deviceId_; }
QString homeserver() const { return homeserver_; }
bool disableCertificateValidation() const { return disableCertificateValidation_; }
QStringList hiddenTags() const { return hiddenTags_; }
bool useIdenticon() const { return useIdenticon_ && JdenticonProvider::isAvailable(); }
return emojiFont_;
}
Presence presence() const { return presence_; }
QString ringtone() const { return ringtone_; }
QString microphone() const { return microphone_; }
QString camera() const { return camera_; }
QString cameraResolution() const { return cameraResolution_; }
QString cameraFrameRate() const { return cameraFrameRate_; }
int screenShareFrameRate() const { return screenShareFrameRate_; }
bool screenSharePiP() const { return screenSharePiP_; }
bool screenShareRemoteVideo() const { return screenShareRemoteVideo_; }
bool screenShareHideCursor() const { return screenShareHideCursor_; }
bool useStunServer() const { return useStunServer_; }
bool shareKeysWithTrustedUsers() const { return shareKeysWithTrustedUsers_; }
bool onlyShareKeysWithVerifiedUsers() const { return onlyShareKeysWithVerifiedUsers_; }
bool useOnlineKeyBackup() const { return useOnlineKeyBackup_; }
QString profile() const { return profile_; }
QString userId() const { return userId_; }
QString accessToken() const { return accessToken_; }
QString deviceId() const { return deviceId_; }
QString homeserver() const { return homeserver_; }
bool disableCertificateValidation() const { return disableCertificateValidation_; }
QStringList hiddenTags() const { return hiddenTags_; }
bool useIdenticon() const { return useIdenticon_ && JdenticonProvider::isAvailable(); }
signals:
void groupViewStateChanged(bool state);
void roomSortingChanged(bool state);
void themeChanged(QString state);
void messageHoverHighlightChanged(bool state);
void enlargeEmojiOnlyMessagesChanged(bool state);
void trayChanged(bool state);
void startInTrayChanged(bool state);
void markdownChanged(bool state);
void animateImagesOnHoverChanged(bool state);
void typingNotificationsChanged(bool state);
void buttonInTimelineChanged(bool state);
void readReceiptsChanged(bool state);
void desktopNotificationsChanged(bool state);
void alertOnNotificationChanged(bool state);
void avatarCirclesChanged(bool state);
void decryptSidebarChanged(bool state);
void privacyScreenChanged(bool state);
void privacyScreenTimeoutChanged(int state);
void timelineMaxWidthChanged(int state);
void roomListWidthChanged(int state);
void communityListWidthChanged(int state);
void mobileModeChanged(bool mode);
void fontSizeChanged(double state);
void fontChanged(QString state);
void emojiFontChanged(QString state);
void presenceChanged(Presence state);
void ringtoneChanged(QString ringtone);
void microphoneChanged(QString microphone);
void cameraChanged(QString camera);
void cameraResolutionChanged(QString resolution);
void cameraFrameRateChanged(QString frameRate);
void screenShareFrameRateChanged(int frameRate);
void screenSharePiPChanged(bool state);
void screenShareRemoteVideoChanged(bool state);
void screenShareHideCursorChanged(bool state);
void useStunServerChanged(bool state);
void onlyShareKeysWithVerifiedUsersChanged(bool state);
void shareKeysWithTrustedUsersChanged(bool state);
void useOnlineKeyBackupChanged(bool state);
void profileChanged(QString profile);
void userIdChanged(QString userId);
void accessTokenChanged(QString accessToken);
void deviceIdChanged(QString deviceId);
void homeserverChanged(QString homeserver);
void disableCertificateValidationChanged(bool disabled);
void useIdenticonChanged(bool state);
void groupViewStateChanged(bool state);
void roomSortingChanged(bool state);
void themeChanged(QString state);
void messageHoverHighlightChanged(bool state);
void enlargeEmojiOnlyMessagesChanged(bool state);
void trayChanged(bool state);
void startInTrayChanged(bool state);
void markdownChanged(bool state);
void animateImagesOnHoverChanged(bool state);
void typingNotificationsChanged(bool state);
void buttonInTimelineChanged(bool state);
void readReceiptsChanged(bool state);
void desktopNotificationsChanged(bool state);
void alertOnNotificationChanged(bool state);
void avatarCirclesChanged(bool state);
void decryptSidebarChanged(bool state);
void privacyScreenChanged(bool state);
void privacyScreenTimeoutChanged(int state);
void timelineMaxWidthChanged(int state);
void roomListWidthChanged(int state);
void communityListWidthChanged(int state);
void mobileModeChanged(bool mode);
void fontSizeChanged(double state);
void fontChanged(QString state);
void emojiFontChanged(QString state);
void presenceChanged(Presence state);
void ringtoneChanged(QString ringtone);
void microphoneChanged(QString microphone);
void cameraChanged(QString camera);
void cameraResolutionChanged(QString resolution);
void cameraFrameRateChanged(QString frameRate);
void screenShareFrameRateChanged(int frameRate);
void screenSharePiPChanged(bool state);
void screenShareRemoteVideoChanged(bool state);
void screenShareHideCursorChanged(bool state);
void useStunServerChanged(bool state);
void onlyShareKeysWithVerifiedUsersChanged(bool state);
void shareKeysWithTrustedUsersChanged(bool state);
void useOnlineKeyBackupChanged(bool state);
void profileChanged(QString profile);
void userIdChanged(QString userId);
void accessTokenChanged(QString accessToken);
void deviceIdChanged(QString deviceId);
void homeserverChanged(QString homeserver);
void disableCertificateValidationChanged(bool disabled);
void useIdenticonChanged(bool state);
private:
// Default to system theme if QT_QPA_PLATFORMTHEME var is set.
QString defaultTheme_ =
QProcessEnvironment::systemEnvironment().value("QT_QPA_PLATFORMTHEME", "").isEmpty()
? "light"
: "system";
QString theme_;
bool messageHoverHighlight_;
bool enlargeEmojiOnlyMessages_;
bool tray_;
bool startInTray_;
bool groupView_;
bool markdown_;
bool animateImagesOnHover_;
bool typingNotifications_;
bool sortByImportance_;
bool buttonsInTimeline_;
bool readReceipts_;
bool hasDesktopNotifications_;
bool hasAlertOnNotification_;
bool avatarCircles_;
bool decryptSidebar_;
bool privacyScreen_;
int privacyScreenTimeout_;
bool shareKeysWithTrustedUsers_;
bool onlyShareKeysWithVerifiedUsers_;
bool useOnlineKeyBackup_;
bool mobileMode_;
int timelineMaxWidth_;
int roomListWidth_;
int communityListWidth_;
double baseFontSize_;
QString font_;
QString emojiFont_;
Presence presence_;
QString ringtone_;
QString microphone_;
QString camera_;
QString cameraResolution_;
QString cameraFrameRate_;
int screenShareFrameRate_;
bool screenSharePiP_;
bool screenShareRemoteVideo_;
bool screenShareHideCursor_;
bool useStunServer_;
bool disableCertificateValidation_ = false;
QString profile_;
QString userId_;
QString accessToken_;
QString deviceId_;
QString homeserver_;
QStringList hiddenTags_;
bool useIdenticon_;
QSettings settings;
static QSharedPointer<UserSettings> instance_;
// Default to system theme if QT_QPA_PLATFORMTHEME var is set.
QString defaultTheme_ =
QProcessEnvironment::systemEnvironment().value("QT_QPA_PLATFORMTHEME", "").isEmpty()
? "light"
: "system";
QString theme_;
bool messageHoverHighlight_;
bool enlargeEmojiOnlyMessages_;
bool tray_;
bool startInTray_;
bool groupView_;
bool markdown_;
bool animateImagesOnHover_;
bool typingNotifications_;
bool sortByImportance_;
bool buttonsInTimeline_;
bool readReceipts_;
bool hasDesktopNotifications_;
bool hasAlertOnNotification_;
bool avatarCircles_;
bool decryptSidebar_;
bool privacyScreen_;
int privacyScreenTimeout_;
bool shareKeysWithTrustedUsers_;
bool onlyShareKeysWithVerifiedUsers_;
bool useOnlineKeyBackup_;
bool mobileMode_;
int timelineMaxWidth_;
int roomListWidth_;
int communityListWidth_;
double baseFontSize_;
QString font_;
QString emojiFont_;
Presence presence_;
QString ringtone_;
QString microphone_;
QString camera_;
QString cameraResolution_;
QString cameraFrameRate_;
int screenShareFrameRate_;
bool screenSharePiP_;
bool screenShareRemoteVideo_;
bool screenShareHideCursor_;
bool useStunServer_;
bool disableCertificateValidation_ = false;
QString profile_;
QString userId_;
QString accessToken_;
QString deviceId_;
QString homeserver_;
QStringList hiddenTags_;
bool useIdenticon_;
QSettings settings;
static QSharedPointer<UserSettings> instance_;
};
class HorizontalLine : public QFrame
{
Q_OBJECT
Q_OBJECT
public:
HorizontalLine(QWidget *parent = nullptr);
HorizontalLine(QWidget *parent = nullptr);
};
class UserSettingsPage : public QWidget
{
Q_OBJECT
Q_OBJECT
public:
UserSettingsPage(QSharedPointer<UserSettings> settings, QWidget *parent = nullptr);
UserSettingsPage(QSharedPointer<UserSettings> settings, QWidget *parent = nullptr);
protected:
void showEvent(QShowEvent *event) override;
void paintEvent(QPaintEvent *event) override;
void showEvent(QShowEvent *event) override;
void paintEvent(QPaintEvent *event) override;
signals:
void moveBack();
void trayOptionChanged(bool value);
void themeChanged();
void decryptSidebarChanged();
void moveBack();
void trayOptionChanged(bool value);
void themeChanged();
void decryptSidebarChanged();
public slots:
void updateSecretStatus();
void updateSecretStatus();
private slots:
void importSessionKeys();
void exportSessionKeys();
void importSessionKeys();
void exportSessionKeys();
private:
// Layouts
QVBoxLayout *topLayout_;
QHBoxLayout *topBarLayout_;
QFormLayout *formLayout_;
// Shared settings object.
QSharedPointer<UserSettings> settings_;
Toggle *trayToggle_;
Toggle *startInTrayToggle_;
Toggle *groupViewToggle_;
Toggle *timelineButtonsToggle_;
Toggle *typingNotifications_;
Toggle *messageHoverHighlight_;
Toggle *enlargeEmojiOnlyMessages_;
Toggle *sortByImportance_;
Toggle *readReceipts_;
Toggle *markdown_;
Toggle *animateImagesOnHover_;
Toggle *desktopNotifications_;
Toggle *alertOnNotification_;
Toggle *avatarCircles_;
Toggle *useIdenticon_;
Toggle *useStunServer_;
Toggle *decryptSidebar_;
Toggle *privacyScreen_;
QSpinBox *privacyScreenTimeout_;
Toggle *shareKeysWithTrustedUsers_;
Toggle *onlyShareKeysWithVerifiedUsers_;
Toggle *useOnlineKeyBackup_;
Toggle *mobileMode_;
QLabel *deviceFingerprintValue_;
QLabel *deviceIdValue_;
QLabel *backupSecretCached;
QLabel *masterSecretCached;
QLabel *selfSigningSecretCached;
QLabel *userSigningSecretCached;
QComboBox *themeCombo_;
QComboBox *scaleFactorCombo_;
QComboBox *fontSizeCombo_;
QFontComboBox *fontSelectionCombo_;
QComboBox *emojiFontSelectionCombo_;
QComboBox *ringtoneCombo_;
QComboBox *microphoneCombo_;
QComboBox *cameraCombo_;
QComboBox *cameraResolutionCombo_;
QComboBox *cameraFrameRateCombo_;
QSpinBox *timelineMaxWidthSpin_;
int sideMargin_ = 0;
// Layouts
QVBoxLayout *topLayout_;
QHBoxLayout *topBarLayout_;
QFormLayout *formLayout_;
// Shared settings object.
QSharedPointer<UserSettings> settings_;
Toggle *trayToggle_;
Toggle *startInTrayToggle_;
Toggle *groupViewToggle_;
Toggle *timelineButtonsToggle_;
Toggle *typingNotifications_;
Toggle *messageHoverHighlight_;
Toggle *enlargeEmojiOnlyMessages_;
Toggle *sortByImportance_;
Toggle *readReceipts_;
Toggle *markdown_;
Toggle *animateImagesOnHover_;
Toggle *desktopNotifications_;
Toggle *alertOnNotification_;
Toggle *avatarCircles_;
Toggle *useIdenticon_;
Toggle *useStunServer_;
Toggle *decryptSidebar_;
Toggle *privacyScreen_;
QSpinBox *privacyScreenTimeout_;
Toggle *shareKeysWithTrustedUsers_;
Toggle *onlyShareKeysWithVerifiedUsers_;
Toggle *useOnlineKeyBackup_;
Toggle *mobileMode_;
QLabel *deviceFingerprintValue_;
QLabel *deviceIdValue_;
QLabel *backupSecretCached;
QLabel *masterSecretCached;
QLabel *selfSigningSecretCached;
QLabel *userSigningSecretCached;
QComboBox *themeCombo_;
QComboBox *scaleFactorCombo_;
QComboBox *fontSizeCombo_;
QFontComboBox *fontSelectionCombo_;
QComboBox *emojiFontSelectionCombo_;
QComboBox *ringtoneCombo_;
QComboBox *microphoneCombo_;
QComboBox *cameraCombo_;
QComboBox *cameraResolutionCombo_;
QComboBox *cameraFrameRateCombo_;
QSpinBox *timelineMaxWidthSpin_;
int sideMargin_ = 0;
};

@ -14,51 +14,51 @@ UsersModel::UsersModel(const std::string &roomId, QObject *parent)
: QAbstractListModel(parent)
, room_id(roomId)
{
roomMembers_ = cache::roomMembers(roomId);
for (const auto &m : roomMembers_) {
displayNames.push_back(QString::fromStdString(cache::displayName(room_id, m)));
userids.push_back(QString::fromStdString(m));
}
roomMembers_ = cache::roomMembers(roomId);
for (const auto &m : roomMembers_) {
displayNames.push_back(QString::fromStdString(cache::displayName(room_id, m)));
userids.push_back(QString::fromStdString(m));
}
}
QHash<int, QByteArray>
UsersModel::roleNames() const
{
return {
{CompletionModel::CompletionRole, "completionRole"},
{CompletionModel::SearchRole, "searchRole"},
{CompletionModel::SearchRole2, "searchRole2"},
{Roles::DisplayName, "displayName"},
{Roles::AvatarUrl, "avatarUrl"},
{Roles::UserID, "userid"},
};
return {
{CompletionModel::CompletionRole, "completionRole"},
{CompletionModel::SearchRole, "searchRole"},
{CompletionModel::SearchRole2, "searchRole2"},
{Roles::DisplayName, "displayName"},
{Roles::AvatarUrl, "avatarUrl"},
{Roles::UserID, "userid"},
};
}
QVariant
UsersModel::data(const QModelIndex &index, int role) const
{
if (hasIndex(index.row(), index.column(), index.parent())) {
switch (role) {
case CompletionModel::CompletionRole:
if (UserSettings::instance()->markdown())
return QString("[%1](https://matrix.to/#/%2)")
.arg(displayNames[index.row()].toHtmlEscaped())
.arg(QString(QUrl::toPercentEncoding(userids[index.row()])));
else
return displayNames[index.row()];
case CompletionModel::SearchRole:
return displayNames[index.row()];
case Qt::DisplayRole:
case Roles::DisplayName:
return displayNames[index.row()].toHtmlEscaped();
case CompletionModel::SearchRole2:
return userids[index.row()];
case Roles::AvatarUrl:
return cache::avatarUrl(QString::fromStdString(room_id),
QString::fromStdString(roomMembers_[index.row()]));
case Roles::UserID:
return userids[index.row()].toHtmlEscaped();
}
if (hasIndex(index.row(), index.column(), index.parent())) {
switch (role) {
case CompletionModel::CompletionRole:
if (UserSettings::instance()->markdown())
return QString("[%1](https://matrix.to/#/%2)")
.arg(displayNames[index.row()].toHtmlEscaped())
.arg(QString(QUrl::toPercentEncoding(userids[index.row()])));
else
return displayNames[index.row()];
case CompletionModel::SearchRole:
return displayNames[index.row()];
case Qt::DisplayRole:
case Roles::DisplayName:
return displayNames[index.row()].toHtmlEscaped();
case CompletionModel::SearchRole2:
return userids[index.row()];
case Roles::AvatarUrl:
return cache::avatarUrl(QString::fromStdString(room_id),
QString::fromStdString(roomMembers_[index.row()]));
case Roles::UserID:
return userids[index.row()].toHtmlEscaped();
}
return {};
}
return {};
}

@ -9,25 +9,25 @@
class UsersModel : public QAbstractListModel
{
public:
enum Roles
{
AvatarUrl = Qt::UserRole,
DisplayName,
UserID,
};
enum Roles
{
AvatarUrl = Qt::UserRole,
DisplayName,
UserID,
};
UsersModel(const std::string &roomId, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
(void)parent;
return (int)roomMembers_.size();
}
QVariant data(const QModelIndex &index, int role) const override;
UsersModel(const std::string &roomId, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
(void)parent;
return (int)roomMembers_.size();
}
QVariant data(const QModelIndex &index, int role) const override;
private:
std::string room_id;
std::vector<std::string> roomMembers_;
std::vector<QString> displayNames;
std::vector<QString> userids;
std::string room_id;
std::vector<std::string> roomMembers_;
std::vector<QString> displayNames;
std::vector<QString> userids;
};

File diff suppressed because it is too large Load Diff

@ -29,12 +29,12 @@ class QComboBox;
// outgoing messages
struct RelatedInfo
{
using MsgType = mtx::events::MessageType;
MsgType type;
QString room;
QString quoted_body, quoted_formatted_body;
std::string related_event;
QString quoted_user;
using MsgType = mtx::events::MessageType;
MsgType type;
QString room;
QString quoted_body, quoted_formatted_body;
std::string related_event;
QString quoted_user;
};
namespace utils {
@ -97,112 +97,96 @@ messageDescription(const QString &username = "",
const QString &body = "",
const bool isLocal = false)
{
using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>;
using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>;
using File = mtx::events::RoomEvent<mtx::events::msg::File>;
using Image = mtx::events::RoomEvent<mtx::events::msg::Image>;
using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>;
using Sticker = mtx::events::Sticker;
using Text = mtx::events::RoomEvent<mtx::events::msg::Text>;
using Video = mtx::events::RoomEvent<mtx::events::msg::Video>;
using CallInvite = mtx::events::RoomEvent<mtx::events::msg::CallInvite>;
using CallAnswer = mtx::events::RoomEvent<mtx::events::msg::CallAnswer>;
using CallHangUp = mtx::events::RoomEvent<mtx::events::msg::CallHangUp>;
using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>;
if (std::is_same<T, Audio>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:",
"You sent an audio clip");
else
return QCoreApplication::translate("message-description sent:",
"%1 sent an audio clip")
.arg(username);
} else if (std::is_same<T, Image>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:",
"You sent an image");
else
return QCoreApplication::translate("message-description sent:",
"%1 sent an image")
.arg(username);
} else if (std::is_same<T, File>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:",
"You sent a file");
else
return QCoreApplication::translate("message-description sent:",
"%1 sent a file")
.arg(username);
} else if (std::is_same<T, Video>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:",
"You sent a video");
else
return QCoreApplication::translate("message-description sent:",
"%1 sent a video")
.arg(username);
} else if (std::is_same<T, Sticker>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:",
"You sent a sticker");
else
return QCoreApplication::translate("message-description sent:",
"%1 sent a sticker")
.arg(username);
} else if (std::is_same<T, Notice>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:",
"You sent a notification");
else
return QCoreApplication::translate("message-description sent:",
"%1 sent a notification")
.arg(username);
} else if (std::is_same<T, Text>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:", "You: %1")
.arg(body);
else
return QCoreApplication::translate("message-description sent:", "%1: %2")
.arg(username)
.arg(body);
} else if (std::is_same<T, Emote>::value) {
return QString("* %1 %2").arg(username).arg(body);
} else if (std::is_same<T, Encrypted>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:",
"You sent an encrypted message");
else
return QCoreApplication::translate("message-description sent:",
"%1 sent an encrypted message")
.arg(username);
} else if (std::is_same<T, CallInvite>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:",
"You placed a call");
else
return QCoreApplication::translate("message-description sent:",
"%1 placed a call")
.arg(username);
} else if (std::is_same<T, CallAnswer>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:",
"You answered a call");
else
return QCoreApplication::translate("message-description sent:",
"%1 answered a call")
.arg(username);
} else if (std::is_same<T, CallHangUp>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:",
"You ended a call");
else
return QCoreApplication::translate("message-description sent:",
"%1 ended a call")
.arg(username);
} else {
return QCoreApplication::translate("utils", "Unknown Message Type");
}
using Audio = mtx::events::RoomEvent<mtx::events::msg::Audio>;
using Emote = mtx::events::RoomEvent<mtx::events::msg::Emote>;
using File = mtx::events::RoomEvent<mtx::events::msg::File>;
using Image = mtx::events::RoomEvent<mtx::events::msg::Image>;
using Notice = mtx::events::RoomEvent<mtx::events::msg::Notice>;
using Sticker = mtx::events::Sticker;
using Text = mtx::events::RoomEvent<mtx::events::msg::Text>;
using Video = mtx::events::RoomEvent<mtx::events::msg::Video>;
using CallInvite = mtx::events::RoomEvent<mtx::events::msg::CallInvite>;
using CallAnswer = mtx::events::RoomEvent<mtx::events::msg::CallAnswer>;
using CallHangUp = mtx::events::RoomEvent<mtx::events::msg::CallHangUp>;
using Encrypted = mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>;
if (std::is_same<T, Audio>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:",
"You sent an audio clip");
else
return QCoreApplication::translate("message-description sent:", "%1 sent an audio clip")
.arg(username);
} else if (std::is_same<T, Image>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:", "You sent an image");
else
return QCoreApplication::translate("message-description sent:", "%1 sent an image")
.arg(username);
} else if (std::is_same<T, File>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:", "You sent a file");
else
return QCoreApplication::translate("message-description sent:", "%1 sent a file")
.arg(username);
} else if (std::is_same<T, Video>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:", "You sent a video");
else
return QCoreApplication::translate("message-description sent:", "%1 sent a video")
.arg(username);
} else if (std::is_same<T, Sticker>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:", "You sent a sticker");
else
return QCoreApplication::translate("message-description sent:", "%1 sent a sticker")
.arg(username);
} else if (std::is_same<T, Notice>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:",
"You sent a notification");
else
return QCoreApplication::translate("message-description sent:",
"%1 sent a notification")
.arg(username);
} else if (std::is_same<T, Text>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:", "You: %1").arg(body);
else
return QCoreApplication::translate("message-description sent:", "%1: %2")
.arg(username)
.arg(body);
} else if (std::is_same<T, Emote>::value) {
return QString("* %1 %2").arg(username).arg(body);
} else if (std::is_same<T, Encrypted>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:",
"You sent an encrypted message");
else
return QCoreApplication::translate("message-description sent:",
"%1 sent an encrypted message")
.arg(username);
} else if (std::is_same<T, CallInvite>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:", "You placed a call");
else
return QCoreApplication::translate("message-description sent:", "%1 placed a call")
.arg(username);
} else if (std::is_same<T, CallAnswer>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:", "You answered a call");
else
return QCoreApplication::translate("message-description sent:", "%1 answered a call")
.arg(username);
} else if (std::is_same<T, CallHangUp>::value) {
if (isLocal)
return QCoreApplication::translate("message-description sent:", "You ended a call");
else
return QCoreApplication::translate("message-description sent:", "%1 ended a call")
.arg(username);
} else {
return QCoreApplication::translate("utils", "Unknown Message Type");
}
}
//! Scale down an image to fit to the given width & height limitations.
@ -214,19 +198,19 @@ template<typename ContainerT, typename PredicateT>
void
erase_if(ContainerT &items, const PredicateT &predicate)
{
for (auto it = items.begin(); it != items.end();) {
if (predicate(*it))
it = items.erase(it);
else
++it;
}
for (auto it = items.begin(); it != items.end();) {
if (predicate(*it))
it = items.erase(it);
else
++it;
}
}
template<class T>
QString
message_body(const mtx::events::collections::TimelineEvents &event)
{
return QString::fromStdString(std::get<T>(event).content.body);
return QString::fromStdString(std::get<T>(event).content.body);
}
//! Calculate the Levenshtein distance between two strings with character skipping.
@ -253,13 +237,13 @@ template<typename RoomMessageT>
QString
getMessageBody(const RoomMessageT &event)
{
if (event.content.format.empty())
return QString::fromStdString(event.content.body).toHtmlEscaped();
if (event.content.format.empty())
return QString::fromStdString(event.content.body).toHtmlEscaped();
if (event.content.format != mtx::common::FORMAT_MSG_TYPE)
return QString::fromStdString(event.content.body).toHtmlEscaped();
if (event.content.format != mtx::common::FORMAT_MSG_TYPE)
return QString::fromStdString(event.content.body).toHtmlEscaped();
return QString::fromStdString(event.content.formatted_body);
return QString::fromStdString(event.content.formatted_body);
}
//! Replace raw URLs in text with HTML link tags.

File diff suppressed because it is too large Load Diff

@ -20,22 +20,22 @@ Q_NAMESPACE
enum class CallType
{
VOICE,
VIDEO,
SCREEN // localUser is sharing screen
VOICE,
VIDEO,
SCREEN // localUser is sharing screen
};
Q_ENUM_NS(CallType)
enum class State
{
DISCONNECTED,
ICEFAILED,
INITIATING,
INITIATED,
OFFERSENT,
ANSWERSENT,
CONNECTING,
CONNECTED
DISCONNECTED,
ICEFAILED,
INITIATING,
INITIATED,
OFFERSENT,
ANSWERSENT,
CONNECTING,
CONNECTED
};
Q_ENUM_NS(State)
@ -43,75 +43,75 @@ Q_ENUM_NS(State)
class WebRTCSession : public QObject
{
Q_OBJECT
Q_OBJECT
public:
static WebRTCSession &instance()
{
static WebRTCSession instance;
return instance;
}
bool havePlugins(bool isVideo, std::string *errorMessage = nullptr);
webrtc::CallType callType() const { return callType_; }
webrtc::State state() const { return state_; }
bool haveLocalPiP() const;
bool isOffering() const { return isOffering_; }
bool isRemoteVideoRecvOnly() const { return isRemoteVideoRecvOnly_; }
bool isRemoteVideoSendOnly() const { return isRemoteVideoSendOnly_; }
bool createOffer(webrtc::CallType, uint32_t shareWindowId);
bool acceptOffer(const std::string &sdp);
bool acceptAnswer(const std::string &sdp);
void acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
bool isMicMuted() const;
bool toggleMicMute();
void toggleLocalPiP();
void end();
void setTurnServers(const std::vector<std::string> &uris) { turnServers_ = uris; }
void setVideoItem(QQuickItem *item) { videoItem_ = item; }
QQuickItem *getVideoItem() const { return videoItem_; }
static WebRTCSession &instance()
{
static WebRTCSession instance;
return instance;
}
bool havePlugins(bool isVideo, std::string *errorMessage = nullptr);
webrtc::CallType callType() const { return callType_; }
webrtc::State state() const { return state_; }
bool haveLocalPiP() const;
bool isOffering() const { return isOffering_; }
bool isRemoteVideoRecvOnly() const { return isRemoteVideoRecvOnly_; }
bool isRemoteVideoSendOnly() const { return isRemoteVideoSendOnly_; }
bool createOffer(webrtc::CallType, uint32_t shareWindowId);
bool acceptOffer(const std::string &sdp);
bool acceptAnswer(const std::string &sdp);
void acceptICECandidates(const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
bool isMicMuted() const;
bool toggleMicMute();
void toggleLocalPiP();
void end();
void setTurnServers(const std::vector<std::string> &uris) { turnServers_ = uris; }
void setVideoItem(QQuickItem *item) { videoItem_ = item; }
QQuickItem *getVideoItem() const { return videoItem_; }
signals:
void offerCreated(const std::string &sdp,
const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
void answerCreated(const std::string &sdp,
const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
void newICECandidate(const mtx::events::msg::CallCandidates::Candidate &);
void stateChanged(webrtc::State);
void offerCreated(const std::string &sdp,
const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
void answerCreated(const std::string &sdp,
const std::vector<mtx::events::msg::CallCandidates::Candidate> &);
void newICECandidate(const mtx::events::msg::CallCandidates::Candidate &);
void stateChanged(webrtc::State);
private slots:
void setState(webrtc::State state) { state_ = state; }
void setState(webrtc::State state) { state_ = state; }
private:
WebRTCSession();
CallDevices &devices_;
bool initialised_ = false;
bool haveVoicePlugins_ = false;
bool haveVideoPlugins_ = false;
webrtc::CallType callType_ = webrtc::CallType::VOICE;
webrtc::State state_ = webrtc::State::DISCONNECTED;
bool isOffering_ = false;
bool isRemoteVideoRecvOnly_ = false;
bool isRemoteVideoSendOnly_ = false;
QQuickItem *videoItem_ = nullptr;
GstElement *pipe_ = nullptr;
GstElement *webrtc_ = nullptr;
unsigned int busWatchId_ = 0;
std::vector<std::string> turnServers_;
uint32_t shareWindowId_ = 0;
bool init(std::string *errorMessage = nullptr);
bool startPipeline(int opusPayloadType, int vp8PayloadType);
bool createPipeline(int opusPayloadType, int vp8PayloadType);
bool addVideoPipeline(int vp8PayloadType);
void clear();
WebRTCSession();
CallDevices &devices_;
bool initialised_ = false;
bool haveVoicePlugins_ = false;
bool haveVideoPlugins_ = false;
webrtc::CallType callType_ = webrtc::CallType::VOICE;
webrtc::State state_ = webrtc::State::DISCONNECTED;
bool isOffering_ = false;
bool isRemoteVideoRecvOnly_ = false;
bool isRemoteVideoSendOnly_ = false;
QQuickItem *videoItem_ = nullptr;
GstElement *pipe_ = nullptr;
GstElement *webrtc_ = nullptr;
unsigned int busWatchId_ = 0;
std::vector<std::string> turnServers_;
uint32_t shareWindowId_ = 0;
bool init(std::string *errorMessage = nullptr);
bool startPipeline(int opusPayloadType, int vp8PayloadType);
bool createPipeline(int opusPayloadType, int vp8PayloadType);
bool addVideoPipeline(int vp8PayloadType);
void clear();
public:
WebRTCSession(WebRTCSession const &) = delete;
void operator=(WebRTCSession const &) = delete;
WebRTCSession(WebRTCSession const &) = delete;
void operator=(WebRTCSession const &) = delete;
};

@ -16,72 +16,72 @@
WelcomePage::WelcomePage(QWidget *parent)
: QWidget(parent)
{
auto topLayout_ = new QVBoxLayout(this);
topLayout_->setSpacing(20);
topLayout_->setAlignment(Qt::AlignCenter);
auto topLayout_ = new QVBoxLayout(this);
topLayout_->setSpacing(20);
topLayout_->setAlignment(Qt::AlignCenter);
QFont headingFont;
headingFont.setPointSizeF(headingFont.pointSizeF() * 2);
QFont subTitleFont;
subTitleFont.setPointSizeF(subTitleFont.pointSizeF() * 1.5);
QFont headingFont;
headingFont.setPointSizeF(headingFont.pointSizeF() * 2);
QFont subTitleFont;
subTitleFont.setPointSizeF(subTitleFont.pointSizeF() * 1.5);
QIcon icon{QIcon::fromTheme("nheko", QIcon{":/logos/splash.png"})};
QIcon icon{QIcon::fromTheme("nheko", QIcon{":/logos/splash.png"})};
auto logo_ = new QLabel(this);
logo_->setPixmap(icon.pixmap(256));
logo_->setAlignment(Qt::AlignCenter);
auto logo_ = new QLabel(this);
logo_->setPixmap(icon.pixmap(256));
logo_->setAlignment(Qt::AlignCenter);
QString heading(tr("Welcome to nheko! The desktop client for the Matrix protocol."));
QString main(tr("Enjoy your stay!"));
QString heading(tr("Welcome to nheko! The desktop client for the Matrix protocol."));
QString main(tr("Enjoy your stay!"));
auto intoTxt_ = new TextLabel(heading, this);
intoTxt_->setFont(headingFont);
intoTxt_->setAlignment(Qt::AlignCenter);
auto intoTxt_ = new TextLabel(heading, this);
intoTxt_->setFont(headingFont);
intoTxt_->setAlignment(Qt::AlignCenter);
auto subTitle = new TextLabel(main, this);
subTitle->setFont(subTitleFont);
subTitle->setAlignment(Qt::AlignCenter);
auto subTitle = new TextLabel(main, this);
subTitle->setFont(subTitleFont);
subTitle->setAlignment(Qt::AlignCenter);
topLayout_->addStretch(1);
topLayout_->addWidget(logo_);
topLayout_->addWidget(intoTxt_);
topLayout_->addWidget(subTitle);
topLayout_->addStretch(1);
topLayout_->addWidget(logo_);
topLayout_->addWidget(intoTxt_);
topLayout_->addWidget(subTitle);
auto btnLayout_ = new QHBoxLayout();
btnLayout_->setSpacing(20);
btnLayout_->setContentsMargins(0, 20, 0, 20);
auto btnLayout_ = new QHBoxLayout();
btnLayout_->setSpacing(20);
btnLayout_->setContentsMargins(0, 20, 0, 20);
const int fontHeight = QFontMetrics{subTitleFont}.height();
const int buttonHeight = fontHeight * 2.5;
const int buttonWidth = fontHeight * 8;
const int fontHeight = QFontMetrics{subTitleFont}.height();
const int buttonHeight = fontHeight * 2.5;
const int buttonWidth = fontHeight * 8;
auto registerBtn = new RaisedButton(tr("REGISTER"), this);
registerBtn->setMinimumSize(buttonWidth, buttonHeight);
registerBtn->setFontSize(subTitleFont.pointSizeF());
registerBtn->setCornerRadius(conf::btn::cornerRadius);
auto registerBtn = new RaisedButton(tr("REGISTER"), this);
registerBtn->setMinimumSize(buttonWidth, buttonHeight);
registerBtn->setFontSize(subTitleFont.pointSizeF());
registerBtn->setCornerRadius(conf::btn::cornerRadius);
auto loginBtn = new RaisedButton(tr("LOGIN"), this);
loginBtn->setMinimumSize(buttonWidth, buttonHeight);
loginBtn->setFontSize(subTitleFont.pointSizeF());
loginBtn->setCornerRadius(conf::btn::cornerRadius);
auto loginBtn = new RaisedButton(tr("LOGIN"), this);
loginBtn->setMinimumSize(buttonWidth, buttonHeight);
loginBtn->setFontSize(subTitleFont.pointSizeF());
loginBtn->setCornerRadius(conf::btn::cornerRadius);
btnLayout_->addStretch(1);
btnLayout_->addWidget(registerBtn);
btnLayout_->addWidget(loginBtn);
btnLayout_->addStretch(1);
btnLayout_->addStretch(1);
btnLayout_->addWidget(registerBtn);
btnLayout_->addWidget(loginBtn);
btnLayout_->addStretch(1);
topLayout_->addLayout(btnLayout_);
topLayout_->addStretch(1);
topLayout_->addLayout(btnLayout_);
topLayout_->addStretch(1);
connect(registerBtn, &QPushButton::clicked, this, &WelcomePage::userRegister);
connect(loginBtn, &QPushButton::clicked, this, &WelcomePage::userLogin);
connect(registerBtn, &QPushButton::clicked, this, &WelcomePage::userRegister);
connect(loginBtn, &QPushButton::clicked, this, &WelcomePage::userLogin);
}
void
WelcomePage::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}

@ -8,18 +8,18 @@
class WelcomePage : public QWidget
{
Q_OBJECT
Q_OBJECT
public:
explicit WelcomePage(QWidget *parent = nullptr);
explicit WelcomePage(QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent *) override;
void paintEvent(QPaintEvent *) override;
signals:
// Notify that the user wants to login in.
void userLogin();
// Notify that the user wants to login in.
void userLogin();
// Notify that the user wants to register.
void userRegister();
// Notify that the user wants to register.
void userRegister();
};

@ -18,142 +18,142 @@ using namespace dialogs;
CreateRoom::CreateRoom(QWidget *parent)
: QFrame(parent)
{
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
setAttribute(Qt::WA_DeleteOnClose, true);
QFont largeFont;
largeFont.setPointSizeF(largeFont.pointSizeF() * 1.5);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
setMinimumHeight(conf::modals::MIN_WIDGET_HEIGHT);
setMinimumWidth(conf::window::minModalWidth);
auto layout = new QVBoxLayout(this);
layout->setSpacing(conf::modals::WIDGET_SPACING);
layout->setMargin(conf::modals::WIDGET_MARGIN);
auto buttonLayout = new QHBoxLayout();
buttonLayout->setSpacing(15);
confirmBtn_ = new QPushButton(tr("Create room"), this);
confirmBtn_->setDefault(true);
cancelBtn_ = new QPushButton(tr("Cancel"), this);
buttonLayout->addStretch(1);
buttonLayout->addWidget(cancelBtn_);
buttonLayout->addWidget(confirmBtn_);
QFont font;
font.setPointSizeF(font.pointSizeF() * 1.3);
nameInput_ = new TextField(this);
nameInput_->setLabel(tr("Name"));
topicInput_ = new TextField(this);
topicInput_->setLabel(tr("Topic"));
aliasInput_ = new TextField(this);
aliasInput_->setLabel(tr("Alias"));
auto visibilityLayout = new QHBoxLayout;
visibilityLayout->setContentsMargins(0, 10, 0, 10);
auto presetLayout = new QHBoxLayout;
presetLayout->setContentsMargins(0, 10, 0, 10);
auto visibilityLabel = new QLabel(tr("Room Visibility"), this);
visibilityCombo_ = new QComboBox(this);
visibilityCombo_->addItem("Private");
visibilityCombo_->addItem("Public");
visibilityLayout->addWidget(visibilityLabel);
visibilityLayout->addWidget(visibilityCombo_, 0, Qt::AlignBottom | Qt::AlignRight);
auto presetLabel = new QLabel(tr("Room Preset"), this);
presetCombo_ = new QComboBox(this);
presetCombo_->addItem("Private Chat");
presetCombo_->addItem("Public Chat");
presetCombo_->addItem("Trusted Private Chat");
presetLayout->addWidget(presetLabel);
presetLayout->addWidget(presetCombo_, 0, Qt::AlignBottom | Qt::AlignRight);
auto directLabel_ = new QLabel(tr("Direct Chat"), this);
directToggle_ = new Toggle(this);
directToggle_->setActiveColor(QColor("#38A3D8"));
directToggle_->setInactiveColor(QColor("gray"));
directToggle_->setState(false);
auto directLayout = new QHBoxLayout;
directLayout->setContentsMargins(0, 10, 0, 10);
directLayout->addWidget(directLabel_);
directLayout->addWidget(directToggle_, 0, Qt::AlignBottom | Qt::AlignRight);
layout->addWidget(nameInput_);
layout->addWidget(topicInput_);
layout->addWidget(aliasInput_);
layout->addLayout(visibilityLayout);
layout->addLayout(presetLayout);
layout->addLayout(directLayout);
layout->addLayout(buttonLayout);
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
request_.name = nameInput_->text().toStdString();
request_.topic = topicInput_->text().toStdString();
request_.room_alias_name = aliasInput_->text().toStdString();
emit createRoom(request_);
clearFields();
emit close();
});
connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
clearFields();
emit close();
});
connect(visibilityCombo_,
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
[this](const QString &text) {
if (text == "Private") {
request_.visibility = mtx::common::RoomVisibility::Private;
} else {
request_.visibility = mtx::common::RoomVisibility::Public;
}
});
connect(presetCombo_,
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
[this](const QString &text) {
if (text == "Private Chat") {
request_.preset = mtx::requests::Preset::PrivateChat;
} else if (text == "Public Chat") {
request_.preset = mtx::requests::Preset::PublicChat;
} else {
request_.preset = mtx::requests::Preset::TrustedPrivateChat;
}
});
connect(directToggle_, &Toggle::toggled, this, [this](bool isEnabled) {
request_.is_direct = isEnabled;
});
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
setAttribute(Qt::WA_DeleteOnClose, true);
QFont largeFont;
largeFont.setPointSizeF(largeFont.pointSizeF() * 1.5);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
setMinimumHeight(conf::modals::MIN_WIDGET_HEIGHT);
setMinimumWidth(conf::window::minModalWidth);
auto layout = new QVBoxLayout(this);
layout->setSpacing(conf::modals::WIDGET_SPACING);
layout->setMargin(conf::modals::WIDGET_MARGIN);
auto buttonLayout = new QHBoxLayout();
buttonLayout->setSpacing(15);
confirmBtn_ = new QPushButton(tr("Create room"), this);
confirmBtn_->setDefault(true);
cancelBtn_ = new QPushButton(tr("Cancel"), this);
buttonLayout->addStretch(1);
buttonLayout->addWidget(cancelBtn_);
buttonLayout->addWidget(confirmBtn_);
QFont font;
font.setPointSizeF(font.pointSizeF() * 1.3);
nameInput_ = new TextField(this);
nameInput_->setLabel(tr("Name"));
topicInput_ = new TextField(this);
topicInput_->setLabel(tr("Topic"));
aliasInput_ = new TextField(this);
aliasInput_->setLabel(tr("Alias"));
auto visibilityLayout = new QHBoxLayout;
visibilityLayout->setContentsMargins(0, 10, 0, 10);
auto presetLayout = new QHBoxLayout;
presetLayout->setContentsMargins(0, 10, 0, 10);
auto visibilityLabel = new QLabel(tr("Room Visibility"), this);
visibilityCombo_ = new QComboBox(this);
visibilityCombo_->addItem("Private");
visibilityCombo_->addItem("Public");
visibilityLayout->addWidget(visibilityLabel);
visibilityLayout->addWidget(visibilityCombo_, 0, Qt::AlignBottom | Qt::AlignRight);
auto presetLabel = new QLabel(tr("Room Preset"), this);
presetCombo_ = new QComboBox(this);
presetCombo_->addItem("Private Chat");
presetCombo_->addItem("Public Chat");
presetCombo_->addItem("Trusted Private Chat");
presetLayout->addWidget(presetLabel);
presetLayout->addWidget(presetCombo_, 0, Qt::AlignBottom | Qt::AlignRight);
auto directLabel_ = new QLabel(tr("Direct Chat"), this);
directToggle_ = new Toggle(this);
directToggle_->setActiveColor(QColor("#38A3D8"));
directToggle_->setInactiveColor(QColor("gray"));
directToggle_->setState(false);
auto directLayout = new QHBoxLayout;
directLayout->setContentsMargins(0, 10, 0, 10);
directLayout->addWidget(directLabel_);
directLayout->addWidget(directToggle_, 0, Qt::AlignBottom | Qt::AlignRight);
layout->addWidget(nameInput_);
layout->addWidget(topicInput_);
layout->addWidget(aliasInput_);
layout->addLayout(visibilityLayout);
layout->addLayout(presetLayout);
layout->addLayout(directLayout);
layout->addLayout(buttonLayout);
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
request_.name = nameInput_->text().toStdString();
request_.topic = topicInput_->text().toStdString();
request_.room_alias_name = aliasInput_->text().toStdString();
emit createRoom(request_);
clearFields();
emit close();
});
connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
clearFields();
emit close();
});
connect(visibilityCombo_,
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
[this](const QString &text) {
if (text == "Private") {
request_.visibility = mtx::common::RoomVisibility::Private;
} else {
request_.visibility = mtx::common::RoomVisibility::Public;
}
});
connect(presetCombo_,
static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentTextChanged),
[this](const QString &text) {
if (text == "Private Chat") {
request_.preset = mtx::requests::Preset::PrivateChat;
} else if (text == "Public Chat") {
request_.preset = mtx::requests::Preset::PublicChat;
} else {
request_.preset = mtx::requests::Preset::TrustedPrivateChat;
}
});
connect(directToggle_, &Toggle::toggled, this, [this](bool isEnabled) {
request_.is_direct = isEnabled;
});
}
void
CreateRoom::clearFields()
{
nameInput_->clear();
topicInput_->clear();
aliasInput_->clear();
nameInput_->clear();
topicInput_->clear();
aliasInput_->clear();
}
void
CreateRoom::showEvent(QShowEvent *event)
{
nameInput_->setFocus();
nameInput_->setFocus();
QFrame::showEvent(event);
QFrame::showEvent(event);
}

@ -17,32 +17,32 @@ namespace dialogs {
class CreateRoom : public QFrame
{
Q_OBJECT
Q_OBJECT
public:
CreateRoom(QWidget *parent = nullptr);
CreateRoom(QWidget *parent = nullptr);
signals:
void createRoom(const mtx::requests::CreateRoom &request);
void createRoom(const mtx::requests::CreateRoom &request);
protected:
void showEvent(QShowEvent *event) override;
void showEvent(QShowEvent *event) override;
private:
void clearFields();
void clearFields();
QComboBox *visibilityCombo_;
QComboBox *presetCombo_;
QComboBox *visibilityCombo_;
QComboBox *presetCombo_;
Toggle *directToggle_;
Toggle *directToggle_;
QPushButton *confirmBtn_;
QPushButton *cancelBtn_;
QPushButton *confirmBtn_;
QPushButton *cancelBtn_;
TextField *nameInput_;
TextField *topicInput_;
TextField *aliasInput_;
TextField *nameInput_;
TextField *topicInput_;
TextField *aliasInput_;
mtx::requests::CreateRoom request_;
mtx::requests::CreateRoom request_;
};
} // dialogs

@ -18,56 +18,56 @@ using namespace dialogs;
FallbackAuth::FallbackAuth(const QString &authType, const QString &session, QWidget *parent)
: QWidget(parent)
{
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
setAttribute(Qt::WA_DeleteOnClose, true);
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
setAttribute(Qt::WA_DeleteOnClose, true);
auto layout = new QVBoxLayout(this);
layout->setSpacing(conf::modals::WIDGET_SPACING);
layout->setMargin(conf::modals::WIDGET_MARGIN);
auto layout = new QVBoxLayout(this);
layout->setSpacing(conf::modals::WIDGET_SPACING);
layout->setMargin(conf::modals::WIDGET_MARGIN);
auto buttonLayout = new QHBoxLayout();
buttonLayout->setSpacing(8);
buttonLayout->setMargin(0);
auto buttonLayout = new QHBoxLayout();
buttonLayout->setSpacing(8);
buttonLayout->setMargin(0);
openBtn_ = new QPushButton(tr("Open Fallback in Browser"), this);
cancelBtn_ = new QPushButton(tr("Cancel"), this);
confirmBtn_ = new QPushButton(tr("Confirm"), this);
confirmBtn_->setDefault(true);
openBtn_ = new QPushButton(tr("Open Fallback in Browser"), this);
cancelBtn_ = new QPushButton(tr("Cancel"), this);
confirmBtn_ = new QPushButton(tr("Confirm"), this);
confirmBtn_->setDefault(true);
buttonLayout->addStretch(1);
buttonLayout->addWidget(openBtn_);
buttonLayout->addWidget(cancelBtn_);
buttonLayout->addWidget(confirmBtn_);
buttonLayout->addStretch(1);
buttonLayout->addWidget(openBtn_);
buttonLayout->addWidget(cancelBtn_);
buttonLayout->addWidget(confirmBtn_);
QFont font;
font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
QFont font;
font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
auto label = new QLabel(
tr("Open the fallback, follow the steps and confirm after completing them."), this);
label->setFont(font);
auto label = new QLabel(
tr("Open the fallback, follow the steps and confirm after completing them."), this);
label->setFont(font);
layout->addWidget(label);
layout->addLayout(buttonLayout);
layout->addWidget(label);
layout->addLayout(buttonLayout);
connect(openBtn_, &QPushButton::clicked, [session, authType]() {
const auto url = QString("https://%1:%2/_matrix/client/r0/auth/%4/"
"fallback/web?session=%3")
.arg(QString::fromStdString(http::client()->server()))
.arg(http::client()->port())
.arg(session)
.arg(authType);
connect(openBtn_, &QPushButton::clicked, [session, authType]() {
const auto url = QString("https://%1:%2/_matrix/client/r0/auth/%4/"
"fallback/web?session=%3")
.arg(QString::fromStdString(http::client()->server()))
.arg(http::client()->port())
.arg(session)
.arg(authType);
QDesktopServices::openUrl(url);
});
QDesktopServices::openUrl(url);
});
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
emit confirmation();
emit close();
});
connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
emit cancel();
emit close();
});
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
emit confirmation();
emit close();
});
connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
emit cancel();
emit close();
});
}

@ -13,18 +13,18 @@ namespace dialogs {
class FallbackAuth : public QWidget
{
Q_OBJECT
Q_OBJECT
public:
FallbackAuth(const QString &authType, const QString &session, QWidget *parent = nullptr);
FallbackAuth(const QString &authType, const QString &session, QWidget *parent = nullptr);
signals:
void confirmation();
void cancel();
void confirmation();
void cancel();
private:
QPushButton *openBtn_;
QPushButton *confirmBtn_;
QPushButton *cancelBtn_;
QPushButton *openBtn_;
QPushButton *confirmBtn_;
QPushButton *cancelBtn_;
};
} // dialogs

@ -19,84 +19,83 @@ ImageOverlay::ImageOverlay(QPixmap image, QWidget *parent)
: QWidget{parent}
, originalImage_{image}
{
setMouseTracking(true);
setParent(nullptr);
setMouseTracking(true);
setParent(nullptr);
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
setAttribute(Qt::WA_NoSystemBackground, true);
setAttribute(Qt::WA_TranslucentBackground, true);
setAttribute(Qt::WA_DeleteOnClose, true);
setWindowState(Qt::WindowFullScreen);
close_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Escape), this);
setAttribute(Qt::WA_NoSystemBackground, true);
setAttribute(Qt::WA_TranslucentBackground, true);
setAttribute(Qt::WA_DeleteOnClose, true);
setWindowState(Qt::WindowFullScreen);
close_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Escape), this);
connect(close_shortcut_, &QShortcut::activated, this, &ImageOverlay::closing);
connect(this, &ImageOverlay::closing, this, &ImageOverlay::close);
connect(close_shortcut_, &QShortcut::activated, this, &ImageOverlay::closing);
connect(this, &ImageOverlay::closing, this, &ImageOverlay::close);
raise();
raise();
}
void
ImageOverlay::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
Q_UNUSED(event);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
// Full screen overlay.
painter.fillRect(QRect(0, 0, width(), height()), QColor(55, 55, 55, 170));
// Full screen overlay.
painter.fillRect(QRect(0, 0, width(), height()), QColor(55, 55, 55, 170));
// Left and Right margins
int outer_margin = width() * 0.12;
int buttonSize = 36;
int margin = outer_margin * 0.1;
// Left and Right margins
int outer_margin = width() * 0.12;
int buttonSize = 36;
int margin = outer_margin * 0.1;
int max_width = width() - 2 * outer_margin;
int max_height = height();
int max_width = width() - 2 * outer_margin;
int max_height = height();
image_ = utils::scaleDown(max_width, max_height, originalImage_);
image_ = utils::scaleDown(max_width, max_height, originalImage_);
int diff_x = max_width - image_.width();
int diff_y = max_height - image_.height();
int diff_x = max_width - image_.width();
int diff_y = max_height - image_.height();
content_ = QRect(outer_margin + diff_x / 2, diff_y / 2, image_.width(), image_.height());
close_button_ = QRect(width() - margin - buttonSize, margin, buttonSize, buttonSize);
save_button_ =
QRect(width() - (2 * margin) - (2 * buttonSize), margin, buttonSize, buttonSize);
content_ = QRect(outer_margin + diff_x / 2, diff_y / 2, image_.width(), image_.height());
close_button_ = QRect(width() - margin - buttonSize, margin, buttonSize, buttonSize);
save_button_ = QRect(width() - (2 * margin) - (2 * buttonSize), margin, buttonSize, buttonSize);
// Draw main content_.
painter.drawPixmap(content_, image_);
// Draw main content_.
painter.drawPixmap(content_, image_);
// Draw top right corner X.
QPen pen;
pen.setCapStyle(Qt::RoundCap);
pen.setWidthF(5);
pen.setColor("gray");
// Draw top right corner X.
QPen pen;
pen.setCapStyle(Qt::RoundCap);
pen.setWidthF(5);
pen.setColor("gray");
auto center = close_button_.center();
auto center = close_button_.center();
painter.setPen(pen);
painter.drawLine(center - QPointF(15, 15), center + QPointF(15, 15));
painter.drawLine(center + QPointF(15, -15), center - QPointF(15, -15));
painter.setPen(pen);
painter.drawLine(center - QPointF(15, 15), center + QPointF(15, 15));
painter.drawLine(center + QPointF(15, -15), center - QPointF(15, -15));
// Draw download button
center = save_button_.center();
painter.drawLine(center - QPointF(0, 15), center + QPointF(0, 15));
painter.drawLine(center - QPointF(15, 0), center + QPointF(0, 15));
painter.drawLine(center + QPointF(0, 15), center + QPointF(15, 0));
// Draw download button
center = save_button_.center();
painter.drawLine(center - QPointF(0, 15), center + QPointF(0, 15));
painter.drawLine(center - QPointF(15, 0), center + QPointF(0, 15));
painter.drawLine(center + QPointF(0, 15), center + QPointF(15, 0));
}
void
ImageOverlay::mousePressEvent(QMouseEvent *event)
{
if (event->button() != Qt::LeftButton)
return;
if (close_button_.contains(event->pos()))
emit closing();
else if (save_button_.contains(event->pos()))
emit saving();
else if (!content_.contains(event->pos()))
emit closing();
if (event->button() != Qt::LeftButton)
return;
if (close_button_.contains(event->pos()))
emit closing();
else if (save_button_.contains(event->pos()))
emit saving();
else if (!content_.contains(event->pos()))
emit closing();
}

@ -14,25 +14,25 @@ namespace dialogs {
class ImageOverlay : public QWidget
{
Q_OBJECT
Q_OBJECT
public:
ImageOverlay(QPixmap image, QWidget *parent = nullptr);
ImageOverlay(QPixmap image, QWidget *parent = nullptr);
protected:
void mousePressEvent(QMouseEvent *event) override;
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void paintEvent(QPaintEvent *event) override;
signals:
void closing();
void saving();
void closing();
void saving();
private:
QPixmap originalImage_;
QPixmap image_;
QPixmap originalImage_;
QPixmap image_;
QRect content_;
QRect close_button_;
QRect save_button_;
QShortcut *close_shortcut_;
QRect content_;
QRect close_button_;
QRect save_button_;
QShortcut *close_shortcut_;
};
} // dialogs

@ -16,58 +16,58 @@ using namespace dialogs;
JoinRoom::JoinRoom(QWidget *parent)
: QFrame(parent)
{
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
setAttribute(Qt::WA_DeleteOnClose, true);
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
setAttribute(Qt::WA_DeleteOnClose, true);
setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
auto layout = new QVBoxLayout(this);
layout->setSpacing(conf::modals::WIDGET_SPACING);
layout->setMargin(conf::modals::WIDGET_MARGIN);
auto layout = new QVBoxLayout(this);
layout->setSpacing(conf::modals::WIDGET_SPACING);
layout->setMargin(conf::modals::WIDGET_MARGIN);
auto buttonLayout = new QHBoxLayout();
buttonLayout->setSpacing(15);
auto buttonLayout = new QHBoxLayout();
buttonLayout->setSpacing(15);
confirmBtn_ = new QPushButton(tr("Join"), this);
confirmBtn_->setDefault(true);
cancelBtn_ = new QPushButton(tr("Cancel"), this);
confirmBtn_ = new QPushButton(tr("Join"), this);
confirmBtn_->setDefault(true);
cancelBtn_ = new QPushButton(tr("Cancel"), this);
buttonLayout->addStretch(1);
buttonLayout->addWidget(cancelBtn_);
buttonLayout->addWidget(confirmBtn_);
buttonLayout->addStretch(1);
buttonLayout->addWidget(cancelBtn_);
buttonLayout->addWidget(confirmBtn_);
roomInput_ = new TextField(this);
roomInput_->setLabel(tr("Room ID or alias"));
roomInput_ = new TextField(this);
roomInput_->setLabel(tr("Room ID or alias"));
layout->addWidget(roomInput_);
layout->addLayout(buttonLayout);
layout->addStretch(1);
layout->addWidget(roomInput_);
layout->addLayout(buttonLayout);
layout->addStretch(1);
connect(roomInput_, &QLineEdit::returnPressed, this, &JoinRoom::handleInput);
connect(confirmBtn_, &QPushButton::clicked, this, &JoinRoom::handleInput);
connect(cancelBtn_, &QPushButton::clicked, this, &JoinRoom::close);
connect(roomInput_, &QLineEdit::returnPressed, this, &JoinRoom::handleInput);
connect(confirmBtn_, &QPushButton::clicked, this, &JoinRoom::handleInput);
connect(cancelBtn_, &QPushButton::clicked, this, &JoinRoom::close);
}
void
JoinRoom::handleInput()
{
if (roomInput_->text().isEmpty())
return;
if (roomInput_->text().isEmpty())
return;
// TODO: input validation with error messages.
emit joinRoom(roomInput_->text());
roomInput_->clear();
// TODO: input validation with error messages.
emit joinRoom(roomInput_->text());
roomInput_->clear();
emit close();
emit close();
}
void
JoinRoom::showEvent(QShowEvent *event)
{
roomInput_->setFocus();
roomInput_->setFocus();
QFrame::showEvent(event);
QFrame::showEvent(event);
}

@ -13,24 +13,24 @@ namespace dialogs {
class JoinRoom : public QFrame
{
Q_OBJECT
Q_OBJECT
public:
JoinRoom(QWidget *parent = nullptr);
JoinRoom(QWidget *parent = nullptr);
signals:
void joinRoom(const QString &room);
void joinRoom(const QString &room);
protected:
void showEvent(QShowEvent *event) override;
void showEvent(QShowEvent *event) override;
private slots:
void handleInput();
void handleInput();
private:
QPushButton *confirmBtn_;
QPushButton *cancelBtn_;
QPushButton *confirmBtn_;
QPushButton *cancelBtn_;
TextField *roomInput_;
TextField *roomInput_;
};
} // dialogs

@ -15,39 +15,39 @@ using namespace dialogs;
LeaveRoom::LeaveRoom(QWidget *parent)
: QFrame(parent)
{
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
setAttribute(Qt::WA_DeleteOnClose, true);
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
setAttribute(Qt::WA_DeleteOnClose, true);
setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
auto layout = new QVBoxLayout(this);
layout->setSpacing(conf::modals::WIDGET_SPACING);
layout->setMargin(conf::modals::WIDGET_MARGIN);
auto layout = new QVBoxLayout(this);
layout->setSpacing(conf::modals::WIDGET_SPACING);
layout->setMargin(conf::modals::WIDGET_MARGIN);
auto buttonLayout = new QHBoxLayout();
buttonLayout->setSpacing(0);
buttonLayout->setMargin(0);
auto buttonLayout = new QHBoxLayout();
buttonLayout->setSpacing(0);
buttonLayout->setMargin(0);
confirmBtn_ = new QPushButton("Leave", this);
cancelBtn_ = new QPushButton(tr("Cancel"), this);
cancelBtn_->setDefault(true);
confirmBtn_ = new QPushButton("Leave", this);
cancelBtn_ = new QPushButton(tr("Cancel"), this);
cancelBtn_->setDefault(true);
buttonLayout->addStretch(1);
buttonLayout->setSpacing(15);
buttonLayout->addWidget(cancelBtn_);
buttonLayout->addWidget(confirmBtn_);
buttonLayout->addStretch(1);
buttonLayout->setSpacing(15);
buttonLayout->addWidget(cancelBtn_);
buttonLayout->addWidget(confirmBtn_);
auto label = new QLabel(tr("Are you sure you want to leave?"), this);
auto label = new QLabel(tr("Are you sure you want to leave?"), this);
layout->addWidget(label);
layout->addLayout(buttonLayout);
layout->addWidget(label);
layout->addLayout(buttonLayout);
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
emit leaving();
emit close();
});
connect(cancelBtn_, &QPushButton::clicked, this, &LeaveRoom::close);
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
emit leaving();
emit close();
});
connect(cancelBtn_, &QPushButton::clicked, this, &LeaveRoom::close);
}

@ -12,15 +12,15 @@ namespace dialogs {
class LeaveRoom : public QFrame
{
Q_OBJECT
Q_OBJECT
public:
explicit LeaveRoom(QWidget *parent = nullptr);
explicit LeaveRoom(QWidget *parent = nullptr);
signals:
void leaving();
void leaving();
private:
QPushButton *confirmBtn_;
QPushButton *cancelBtn_;
QPushButton *confirmBtn_;
QPushButton *cancelBtn_;
};
} // dialogs

@ -15,40 +15,40 @@ using namespace dialogs;
Logout::Logout(QWidget *parent)
: QFrame(parent)
{
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
setAttribute(Qt::WA_DeleteOnClose, true);
setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
auto layout = new QVBoxLayout(this);
layout->setSpacing(conf::modals::WIDGET_SPACING);
layout->setMargin(conf::modals::WIDGET_MARGIN);
auto buttonLayout = new QHBoxLayout();
buttonLayout->setSpacing(0);
buttonLayout->setMargin(0);
confirmBtn_ = new QPushButton("Logout", this);
cancelBtn_ = new QPushButton(tr("Cancel"), this);
cancelBtn_->setDefault(true);
buttonLayout->addStretch(1);
buttonLayout->setSpacing(15);
buttonLayout->addWidget(cancelBtn_);
buttonLayout->addWidget(confirmBtn_);
auto label = new QLabel(tr("Logout. Are you sure?"), this);
layout->addWidget(label);
layout->addLayout(buttonLayout);
layout->addStretch(1);
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
emit loggingOut();
emit close();
});
connect(cancelBtn_, &QPushButton::clicked, this, &Logout::close);
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
setAttribute(Qt::WA_DeleteOnClose, true);
setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
auto layout = new QVBoxLayout(this);
layout->setSpacing(conf::modals::WIDGET_SPACING);
layout->setMargin(conf::modals::WIDGET_MARGIN);
auto buttonLayout = new QHBoxLayout();
buttonLayout->setSpacing(0);
buttonLayout->setMargin(0);
confirmBtn_ = new QPushButton("Logout", this);
cancelBtn_ = new QPushButton(tr("Cancel"), this);
cancelBtn_->setDefault(true);
buttonLayout->addStretch(1);
buttonLayout->setSpacing(15);
buttonLayout->addWidget(cancelBtn_);
buttonLayout->addWidget(confirmBtn_);
auto label = new QLabel(tr("Logout. Are you sure?"), this);
layout->addWidget(label);
layout->addLayout(buttonLayout);
layout->addStretch(1);
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
emit loggingOut();
emit close();
});
connect(cancelBtn_, &QPushButton::clicked, this, &Logout::close);
}

@ -13,15 +13,15 @@ namespace dialogs {
class Logout : public QFrame
{
Q_OBJECT
Q_OBJECT
public:
explicit Logout(QWidget *parent = nullptr);
explicit Logout(QWidget *parent = nullptr);
signals:
void loggingOut();
void loggingOut();
private:
QPushButton *confirmBtn_;
QPushButton *cancelBtn_;
QPushButton *confirmBtn_;
QPushButton *cancelBtn_;
};
} // dialogs

@ -29,188 +29,185 @@ PreviewUploadOverlay::PreviewUploadOverlay(QWidget *parent)
, upload_{tr("Upload"), this}
, cancel_{tr("Cancel"), this}
{
auto hlayout = new QHBoxLayout;
hlayout->addStretch(1);
hlayout->addWidget(&cancel_);
hlayout->addWidget(&upload_);
hlayout->setMargin(0);
auto vlayout = new QVBoxLayout{this};
vlayout->addWidget(&titleLabel_);
vlayout->addWidget(&infoLabel_);
vlayout->addWidget(&fileName_);
vlayout->addLayout(hlayout);
vlayout->setSpacing(conf::modals::WIDGET_SPACING);
vlayout->setMargin(conf::modals::WIDGET_MARGIN);
upload_.setDefault(true);
connect(&upload_, &QPushButton::clicked, [this]() {
emit confirmUpload(data_, mediaType_, fileName_.text());
close();
});
connect(&fileName_, &QLineEdit::returnPressed, this, [this]() {
emit confirmUpload(data_, mediaType_, fileName_.text());
close();
});
connect(&cancel_, &QPushButton::clicked, this, [this]() {
emit aborted();
close();
});
auto hlayout = new QHBoxLayout;
hlayout->addStretch(1);
hlayout->addWidget(&cancel_);
hlayout->addWidget(&upload_);
hlayout->setMargin(0);
auto vlayout = new QVBoxLayout{this};
vlayout->addWidget(&titleLabel_);
vlayout->addWidget(&infoLabel_);
vlayout->addWidget(&fileName_);
vlayout->addLayout(hlayout);
vlayout->setSpacing(conf::modals::WIDGET_SPACING);
vlayout->setMargin(conf::modals::WIDGET_MARGIN);
upload_.setDefault(true);
connect(&upload_, &QPushButton::clicked, [this]() {
emit confirmUpload(data_, mediaType_, fileName_.text());
close();
});
connect(&fileName_, &QLineEdit::returnPressed, this, [this]() {
emit confirmUpload(data_, mediaType_, fileName_.text());
close();
});
connect(&cancel_, &QPushButton::clicked, this, [this]() {
emit aborted();
close();
});
}
void
PreviewUploadOverlay::init()
{
QSize winsize;
QPoint center;
auto window = MainWindow::instance();
if (window) {
winsize = window->frameGeometry().size();
center = window->frameGeometry().center();
} else {
nhlog::ui()->warn("unable to retrieve MainWindow's size");
}
fileName_.setText(QFileInfo{filePath_}.fileName());
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
QFont font;
font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
titleLabel_.setFont(font);
titleLabel_.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
titleLabel_.setAlignment(Qt::AlignCenter);
infoLabel_.setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
fileName_.setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
fileName_.setAlignment(Qt::AlignCenter);
upload_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
cancel_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
if (isImage_) {
infoLabel_.setAlignment(Qt::AlignCenter);
const auto maxWidth = winsize.width() * 0.8;
const auto maxHeight = winsize.height() * 0.8;
// Scale image preview to fit into the application window.
infoLabel_.setPixmap(utils::scaleDown(maxWidth, maxHeight, image_));
move(center.x() - (width() * 0.5), center.y() - (height() * 0.5));
} else {
infoLabel_.setAlignment(Qt::AlignLeft);
}
infoLabel_.setScaledContents(false);
show();
QSize winsize;
QPoint center;
auto window = MainWindow::instance();
if (window) {
winsize = window->frameGeometry().size();
center = window->frameGeometry().center();
} else {
nhlog::ui()->warn("unable to retrieve MainWindow's size");
}
fileName_.setText(QFileInfo{filePath_}.fileName());
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
QFont font;
font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
titleLabel_.setFont(font);
titleLabel_.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
titleLabel_.setAlignment(Qt::AlignCenter);
infoLabel_.setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
fileName_.setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
fileName_.setAlignment(Qt::AlignCenter);
upload_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
cancel_.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
if (isImage_) {
infoLabel_.setAlignment(Qt::AlignCenter);
const auto maxWidth = winsize.width() * 0.8;
const auto maxHeight = winsize.height() * 0.8;
// Scale image preview to fit into the application window.
infoLabel_.setPixmap(utils::scaleDown(maxWidth, maxHeight, image_));
move(center.x() - (width() * 0.5), center.y() - (height() * 0.5));
} else {
infoLabel_.setAlignment(Qt::AlignLeft);
}
infoLabel_.setScaledContents(false);
show();
}
void
PreviewUploadOverlay::setLabels(const QString &type, const QString &mime, uint64_t upload_size)
{
if (mediaType_.split('/')[0] == "image") {
if (!image_.loadFromData(data_)) {
titleLabel_.setText(QString{tr(ERR_MSG)}.arg(type));
} else {
titleLabel_.setText(QString{tr(DEFAULT)}.arg(mediaType_));
}
isImage_ = true;
if (mediaType_.split('/')[0] == "image") {
if (!image_.loadFromData(data_)) {
titleLabel_.setText(QString{tr(ERR_MSG)}.arg(type));
} else {
auto const info = QString{tr("Media type: %1\n"
"Media size: %2\n")}
.arg(mime)
.arg(utils::humanReadableFileSize(upload_size));
titleLabel_.setText(QString{tr(DEFAULT)}.arg("file"));
infoLabel_.setText(info);
titleLabel_.setText(QString{tr(DEFAULT)}.arg(mediaType_));
}
isImage_ = true;
} else {
auto const info = QString{tr("Media type: %1\n"
"Media size: %2\n")}
.arg(mime)
.arg(utils::humanReadableFileSize(upload_size));
titleLabel_.setText(QString{tr(DEFAULT)}.arg("file"));
infoLabel_.setText(info);
}
}
void
PreviewUploadOverlay::setPreview(const QImage &src, const QString &mime)
{
nhlog::ui()->info("Pasting image with size: {}x{}, format: {}",
src.height(),
src.width(),
mime.toStdString());
auto const &split = mime.split('/');
auto const &type = split[1];
QBuffer buffer(&data_);
buffer.open(QIODevice::WriteOnly);
if (src.save(&buffer, type.toStdString().c_str()))
titleLabel_.setText(QString{tr(DEFAULT)}.arg("image"));
else
titleLabel_.setText(QString{tr(ERR_MSG)}.arg(type));
mediaType_ = mime;
filePath_ = "clipboard." + type;
image_.convertFromImage(src);
isImage_ = true;
nhlog::ui()->info(
"Pasting image with size: {}x{}, format: {}", src.height(), src.width(), mime.toStdString());
auto const &split = mime.split('/');
auto const &type = split[1];
QBuffer buffer(&data_);
buffer.open(QIODevice::WriteOnly);
if (src.save(&buffer, type.toStdString().c_str()))
titleLabel_.setText(QString{tr(DEFAULT)}.arg("image"));
init();
else
titleLabel_.setText(QString{tr(ERR_MSG)}.arg(type));
mediaType_ = mime;
filePath_ = "clipboard." + type;
image_.convertFromImage(src);
isImage_ = true;
titleLabel_.setText(QString{tr(DEFAULT)}.arg("image"));
init();
}
void
PreviewUploadOverlay::setPreview(const QByteArray data, const QString &mime)
{
auto const &split = mime.split('/');
auto const &type = split[1];
auto const &split = mime.split('/');
auto const &type = split[1];
data_ = data;
mediaType_ = mime;
filePath_ = "clipboard." + type;
isImage_ = false;
data_ = data;
mediaType_ = mime;
filePath_ = "clipboard." + type;
isImage_ = false;
setLabels(type, mime, data_.size());
init();
setLabels(type, mime, data_.size());
init();
}
void
PreviewUploadOverlay::setPreview(const QString &path)
{
QFile file{path};
if (!file.open(QIODevice::ReadOnly)) {
nhlog::ui()->warn("Failed to open file ({}): {}",
path.toStdString(),
file.errorString().toStdString());
close();
return;
}
QFile file{path};
QMimeDatabase db;
auto mime = db.mimeTypeForFileNameAndData(path, &file);
if (!file.open(QIODevice::ReadOnly)) {
nhlog::ui()->warn(
"Failed to open file ({}): {}", path.toStdString(), file.errorString().toStdString());
close();
return;
}
if ((data_ = file.readAll()).isEmpty()) {
nhlog::ui()->warn("Failed to read media: {}", file.errorString().toStdString());
close();
return;
}
QMimeDatabase db;
auto mime = db.mimeTypeForFileNameAndData(path, &file);
auto const &split = mime.name().split('/');
if ((data_ = file.readAll()).isEmpty()) {
nhlog::ui()->warn("Failed to read media: {}", file.errorString().toStdString());
close();
return;
}
mediaType_ = mime.name();
filePath_ = file.fileName();
isImage_ = false;
auto const &split = mime.name().split('/');
setLabels(split[1], mime.name(), data_.size());
init();
mediaType_ = mime.name();
filePath_ = file.fileName();
isImage_ = false;
setLabels(split[1], mime.name(), data_.size());
init();
}
void
PreviewUploadOverlay::keyPressEvent(QKeyEvent *event)
{
if (event->matches(QKeySequence::Cancel)) {
emit aborted();
close();
} else {
QWidget::keyPressEvent(event);
}
if (event->matches(QKeySequence::Cancel)) {
emit aborted();
close();
} else {
QWidget::keyPressEvent(event);
}
}

@ -18,35 +18,35 @@ namespace dialogs {
class PreviewUploadOverlay : public QWidget
{
Q_OBJECT
Q_OBJECT
public:
PreviewUploadOverlay(QWidget *parent = nullptr);
PreviewUploadOverlay(QWidget *parent = nullptr);
void setPreview(const QImage &src, const QString &mime);
void setPreview(const QByteArray data, const QString &mime);
void setPreview(const QString &path);
void keyPressEvent(QKeyEvent *event);
void setPreview(const QImage &src, const QString &mime);
void setPreview(const QByteArray data, const QString &mime);
void setPreview(const QString &path);
void keyPressEvent(QKeyEvent *event);
signals:
void confirmUpload(const QByteArray data, const QString &media, const QString &filename);
void aborted();
void confirmUpload(const QByteArray data, const QString &media, const QString &filename);
void aborted();
private:
void init();
void setLabels(const QString &type, const QString &mime, uint64_t upload_size);
void init();
void setLabels(const QString &type, const QString &mime, uint64_t upload_size);
bool isImage_;
QPixmap image_;
bool isImage_;
QPixmap image_;
QByteArray data_;
QString filePath_;
QString mediaType_;
QByteArray data_;
QString filePath_;
QString mediaType_;
QLabel titleLabel_;
QLabel infoLabel_;
QLineEdit fileName_;
QLabel titleLabel_;
QLabel infoLabel_;
QLineEdit fileName_;
QPushButton upload_;
QPushButton cancel_;
QPushButton upload_;
QPushButton cancel_;
};
} // dialogs

@ -18,54 +18,54 @@ using namespace dialogs;
ReCaptcha::ReCaptcha(const QString &session, QWidget *parent)
: QWidget(parent)
{
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
setAttribute(Qt::WA_DeleteOnClose, true);
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
setAttribute(Qt::WA_DeleteOnClose, true);
auto layout = new QVBoxLayout(this);
layout->setSpacing(conf::modals::WIDGET_SPACING);
layout->setMargin(conf::modals::WIDGET_MARGIN);
auto layout = new QVBoxLayout(this);
layout->setSpacing(conf::modals::WIDGET_SPACING);
layout->setMargin(conf::modals::WIDGET_MARGIN);
auto buttonLayout = new QHBoxLayout();
buttonLayout->setSpacing(8);
buttonLayout->setMargin(0);
auto buttonLayout = new QHBoxLayout();
buttonLayout->setSpacing(8);
buttonLayout->setMargin(0);
openCaptchaBtn_ = new QPushButton("Open reCAPTCHA", this);
cancelBtn_ = new QPushButton(tr("Cancel"), this);
confirmBtn_ = new QPushButton(tr("Confirm"), this);
confirmBtn_->setDefault(true);
openCaptchaBtn_ = new QPushButton("Open reCAPTCHA", this);
cancelBtn_ = new QPushButton(tr("Cancel"), this);
confirmBtn_ = new QPushButton(tr("Confirm"), this);
confirmBtn_->setDefault(true);
buttonLayout->addStretch(1);
buttonLayout->addWidget(openCaptchaBtn_);
buttonLayout->addWidget(cancelBtn_);
buttonLayout->addWidget(confirmBtn_);
buttonLayout->addStretch(1);
buttonLayout->addWidget(openCaptchaBtn_);
buttonLayout->addWidget(cancelBtn_);
buttonLayout->addWidget(confirmBtn_);
QFont font;
font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
QFont font;
font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
auto label = new QLabel(tr("Solve the reCAPTCHA and press the confirm button"), this);
label->setFont(font);
auto label = new QLabel(tr("Solve the reCAPTCHA and press the confirm button"), this);
label->setFont(font);
layout->addWidget(label);
layout->addLayout(buttonLayout);
layout->addWidget(label);
layout->addLayout(buttonLayout);
connect(openCaptchaBtn_, &QPushButton::clicked, [session]() {
const auto url = QString("https://%1:%2/_matrix/client/r0/auth/m.login.recaptcha/"
"fallback/web?session=%3")
.arg(QString::fromStdString(http::client()->server()))
.arg(http::client()->port())
.arg(session);
connect(openCaptchaBtn_, &QPushButton::clicked, [session]() {
const auto url = QString("https://%1:%2/_matrix/client/r0/auth/m.login.recaptcha/"
"fallback/web?session=%3")
.arg(QString::fromStdString(http::client()->server()))
.arg(http::client()->port())
.arg(session);
QDesktopServices::openUrl(url);
});
QDesktopServices::openUrl(url);
});
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
emit confirmation();
emit close();
});
connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
emit cancel();
emit close();
});
connect(confirmBtn_, &QPushButton::clicked, this, [this]() {
emit confirmation();
emit close();
});
connect(cancelBtn_, &QPushButton::clicked, this, [this]() {
emit cancel();
emit close();
});
}

@ -12,18 +12,18 @@ namespace dialogs {
class ReCaptcha : public QWidget
{
Q_OBJECT
Q_OBJECT
public:
ReCaptcha(const QString &session, QWidget *parent = nullptr);
ReCaptcha(const QString &session, QWidget *parent = nullptr);
signals:
void confirmation();
void cancel();
void confirmation();
void cancel();
private:
QPushButton *openCaptchaBtn_;
QPushButton *confirmBtn_;
QPushButton *cancelBtn_;
QPushButton *openCaptchaBtn_;
QPushButton *confirmBtn_;
QPushButton *cancelBtn_;
};
} // dialogs

@ -14,63 +14,60 @@ using namespace emoji;
int
EmojiModel::categoryToIndex(int category)
{
auto dist = std::distance(Provider::emoji.begin(),
std::lower_bound(Provider::emoji.begin(),
Provider::emoji.end(),
static_cast<Emoji::Category>(category),
[](const struct Emoji &e, Emoji::Category c) {
return e.category < c;
}));
auto dist = std::distance(
Provider::emoji.begin(),
std::lower_bound(Provider::emoji.begin(),
Provider::emoji.end(),
static_cast<Emoji::Category>(category),
[](const struct Emoji &e, Emoji::Category c) { return e.category < c; }));
return static_cast<int>(dist);
return static_cast<int>(dist);
}
QHash<int, QByteArray>
EmojiModel::roleNames() const
{
static QHash<int, QByteArray> roles;
static QHash<int, QByteArray> roles;
if (roles.isEmpty()) {
roles = QAbstractListModel::roleNames();
roles[static_cast<int>(EmojiModel::Roles::Unicode)] = QByteArrayLiteral("unicode");
roles[static_cast<int>(EmojiModel::Roles::ShortName)] =
QByteArrayLiteral("shortName");
roles[static_cast<int>(EmojiModel::Roles::Category)] =
QByteArrayLiteral("category");
roles[static_cast<int>(EmojiModel::Roles::Emoji)] = QByteArrayLiteral("emoji");
}
if (roles.isEmpty()) {
roles = QAbstractListModel::roleNames();
roles[static_cast<int>(EmojiModel::Roles::Unicode)] = QByteArrayLiteral("unicode");
roles[static_cast<int>(EmojiModel::Roles::ShortName)] = QByteArrayLiteral("shortName");
roles[static_cast<int>(EmojiModel::Roles::Category)] = QByteArrayLiteral("category");
roles[static_cast<int>(EmojiModel::Roles::Emoji)] = QByteArrayLiteral("emoji");
}
return roles;
return roles;
}
int
EmojiModel::rowCount(const QModelIndex &parent) const
{
return parent == QModelIndex() ? Provider::emoji.count() : 0;
return parent == QModelIndex() ? Provider::emoji.count() : 0;
}
QVariant
EmojiModel::data(const QModelIndex &index, int role) const
{
if (hasIndex(index.row(), index.column(), index.parent())) {
switch (role) {
case Qt::DisplayRole:
case CompletionModel::CompletionRole:
case static_cast<int>(EmojiModel::Roles::Unicode):
return Provider::emoji[index.row()].unicode;
if (hasIndex(index.row(), index.column(), index.parent())) {
switch (role) {
case Qt::DisplayRole:
case CompletionModel::CompletionRole:
case static_cast<int>(EmojiModel::Roles::Unicode):
return Provider::emoji[index.row()].unicode;
case Qt::ToolTipRole:
case CompletionModel::SearchRole:
case static_cast<int>(EmojiModel::Roles::ShortName):
return Provider::emoji[index.row()].shortName;
case Qt::ToolTipRole:
case CompletionModel::SearchRole:
case static_cast<int>(EmojiModel::Roles::ShortName):
return Provider::emoji[index.row()].shortName;
case static_cast<int>(EmojiModel::Roles::Category):
return QVariant::fromValue(Provider::emoji[index.row()].category);
case static_cast<int>(EmojiModel::Roles::Category):
return QVariant::fromValue(Provider::emoji[index.row()].category);
case static_cast<int>(EmojiModel::Roles::Emoji):
return QVariant::fromValue(Provider::emoji[index.row()]);
}
case static_cast<int>(EmojiModel::Roles::Emoji):
return QVariant::fromValue(Provider::emoji[index.row()]);
}
}
return {};
return {};
}

@ -18,22 +18,22 @@ namespace emoji {
*/
class EmojiModel : public QAbstractListModel
{
Q_OBJECT
Q_OBJECT
public:
enum Roles
{
Unicode = Qt::UserRole, // unicode of emoji
Category, // category of emoji
ShortName, // shortext of the emoji
Emoji, // Contains everything from the Emoji
};
enum Roles
{
Unicode = Qt::UserRole, // unicode of emoji
Category, // category of emoji
ShortName, // shortext of the emoji
Emoji, // Contains everything from the Emoji
};
using QAbstractListModel::QAbstractListModel;
using QAbstractListModel::QAbstractListModel;
Q_INVOKABLE int categoryToIndex(int category);
Q_INVOKABLE int categoryToIndex(int category);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
};
}

@ -9,6 +9,6 @@
class MacHelper
{
public:
static void showEmojiWindow();
static void initializeMenus();
static void showEmojiWindow();
static void initializeMenus();
};

@ -16,37 +16,37 @@ Q_NAMESPACE
struct Emoji
{
Q_GADGET
Q_GADGET
public:
enum class Category
{
People,
Nature,
Food,
Activity,
Travel,
Objects,
Symbols,
Flags,
Search
};
Q_ENUM(Category)
Q_PROPERTY(const QString &unicode MEMBER unicode)
Q_PROPERTY(const QString &shortName MEMBER shortName)
Q_PROPERTY(emoji::Emoji::Category category MEMBER category)
enum class Category
{
People,
Nature,
Food,
Activity,
Travel,
Objects,
Symbols,
Flags,
Search
};
Q_ENUM(Category)
Q_PROPERTY(const QString &unicode MEMBER unicode)
Q_PROPERTY(const QString &shortName MEMBER shortName)
Q_PROPERTY(emoji::Emoji::Category category MEMBER category)
public:
QString unicode;
QString shortName;
Category category;
QString unicode;
QString shortName;
Category category;
};
class Provider
{
public:
// all emoji for QML purposes
static const QVector<Emoji> emoji;
// all emoji for QML purposes
static const QVector<Emoji> emoji;
};
} // namespace emoji

@ -48,47 +48,47 @@ QQmlDebuggingEnabler enabler;
void
stacktraceHandler(int signum)
{
std::signal(signum, SIG_DFL);
std::signal(signum, SIG_DFL);
// boost::stacktrace::safe_dump_to("./nheko-backtrace.dump");
// boost::stacktrace::safe_dump_to("./nheko-backtrace.dump");
// see
// https://stackoverflow.com/questions/77005/how-to-automatically-generate-a-stacktrace-when-my-program-crashes/77336#77336
void *array[50];
size_t size;
// see
// https://stackoverflow.com/questions/77005/how-to-automatically-generate-a-stacktrace-when-my-program-crashes/77336#77336
void *array[50];
size_t size;
// get void*'s for all entries on the stack
size = backtrace(array, 50);
// get void*'s for all entries on the stack
size = backtrace(array, 50);
// print out all the frames to stderr
fprintf(stderr, "Error: signal %d:\n", signum);
backtrace_symbols_fd(array, size, STDERR_FILENO);
// print out all the frames to stderr
fprintf(stderr, "Error: signal %d:\n", signum);
backtrace_symbols_fd(array, size, STDERR_FILENO);
int file = ::open("/tmp/nheko-crash.dump",
O_CREAT | O_WRONLY | O_TRUNC
int file = ::open("/tmp/nheko-crash.dump",
O_CREAT | O_WRONLY | O_TRUNC
#if defined(S_IWUSR) && defined(S_IRUSR)
,
S_IWUSR | S_IRUSR
,
S_IWUSR | S_IRUSR
#elif defined(S_IWRITE) && defined(S_IREAD)
,
S_IWRITE | S_IREAD
,
S_IWRITE | S_IREAD
#endif
);
if (file != -1) {
constexpr char header[] = "Error: signal\n";
[[maybe_unused]] auto ret = write(file, header, std::size(header) - 1);
backtrace_symbols_fd(array, size, file);
close(file);
}
std::raise(SIGABRT);
);
if (file != -1) {
constexpr char header[] = "Error: signal\n";
[[maybe_unused]] auto ret = write(file, header, std::size(header) - 1);
backtrace_symbols_fd(array, size, file);
close(file);
}
std::raise(SIGABRT);
}
void
registerSignalHandlers()
{
std::signal(SIGSEGV, &stacktraceHandler);
std::signal(SIGABRT, &stacktraceHandler);
std::signal(SIGSEGV, &stacktraceHandler);
std::signal(SIGABRT, &stacktraceHandler);
}
#else
@ -103,203 +103,200 @@ registerSignalHandlers()
QPoint
screenCenter(int width, int height)
{
// Deprecated in 5.13: QRect screenGeometry = QApplication::desktop()->screenGeometry();
QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
// Deprecated in 5.13: QRect screenGeometry = QApplication::desktop()->screenGeometry();
QRect screenGeometry = QGuiApplication::primaryScreen()->geometry();
int x = (screenGeometry.width() - width) / 2;
int y = (screenGeometry.height() - height) / 2;
int x = (screenGeometry.width() - width) / 2;
int y = (screenGeometry.height() - height) / 2;
return QPoint(x, y);
return QPoint(x, y);
}
void
createStandardDirectory(QStandardPaths::StandardLocation path)
{
auto dir = QStandardPaths::writableLocation(path);
auto dir = QStandardPaths::writableLocation(path);
if (!QDir().mkpath(dir)) {
throw std::runtime_error(
("Unable to create state directory:" + dir).toStdString().c_str());
}
if (!QDir().mkpath(dir)) {
throw std::runtime_error(("Unable to create state directory:" + dir).toStdString().c_str());
}
}
int
main(int argc, char *argv[])
{
QCoreApplication::setApplicationName("nheko");
QCoreApplication::setApplicationVersion(nheko::version);
QCoreApplication::setOrganizationName("nheko");
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
// this needs to be after setting the application name. Or how would we find our settings
// file then?
QCoreApplication::setApplicationName("nheko");
QCoreApplication::setApplicationVersion(nheko::version);
QCoreApplication::setOrganizationName("nheko");
QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
// this needs to be after setting the application name. Or how would we find our settings
// file then?
#if defined(Q_OS_LINUX) || defined(Q_OS_WIN) || defined(Q_OS_FREEBSD)
if (qgetenv("QT_SCALE_FACTOR").size() == 0) {
float factor = utils::scaleFactor();
if (qgetenv("QT_SCALE_FACTOR").size() == 0) {
float factor = utils::scaleFactor();
if (factor != -1)
qputenv("QT_SCALE_FACTOR", QString::number(factor).toUtf8());
}
if (factor != -1)
qputenv("QT_SCALE_FACTOR", QString::number(factor).toUtf8());
}
#endif
// This is some hacky programming, but it's necessary (AFAIK?) to get the unique config name
// parsed before the SingleApplication userdata is set.
QString userdata{""};
QString matrixUri;
for (int i = 1; i < argc; ++i) {
QString arg{argv[i]};
if (arg.startsWith("--profile=")) {
arg.remove("--profile=");
userdata = arg;
} else if (arg.startsWith("--p=")) {
arg.remove("-p=");
userdata = arg;
} else if (arg == "--profile" || arg == "-p") {
if (i < argc - 1) // if i is less than argc - 1, we still have a parameter
// left to process as the name
{
++i; // the next arg is the name, so increment
userdata = QString{argv[i]};
}
} else if (arg.startsWith("matrix:")) {
matrixUri = arg;
}
}
SingleApplication app(argc,
argv,
true,
SingleApplication::Mode::User |
SingleApplication::Mode::ExcludeAppPath |
SingleApplication::Mode::ExcludeAppVersion |
SingleApplication::Mode::SecondaryNotification,
100,
userdata);
if (app.isSecondary()) {
// open uri in main instance
app.sendMessage(matrixUri.toUtf8());
return 0;
}
QCommandLineParser parser;
parser.addHelpOption();
parser.addVersionOption();
QCommandLineOption debugOption("debug", "Enable debug output");
parser.addOption(debugOption);
// This option is not actually parsed via Qt due to the need to parse it before the app
// name is set. It only exists to keep Qt from complaining about the --profile/-p
// option and thereby crashing the app.
QCommandLineOption configName(
QStringList() << "p"
<< "profile",
QCoreApplication::tr("Create a unique profile, which allows you to log into several "
"accounts at the same time and start multiple instances of nheko."),
QCoreApplication::tr("profile"),
QCoreApplication::tr("profile name"));
parser.addOption(configName);
parser.process(app);
app.setWindowIcon(QIcon::fromTheme("nheko", QIcon{":/logos/nheko.png"}));
http::init();
createStandardDirectory(QStandardPaths::CacheLocation);
createStandardDirectory(QStandardPaths::AppDataLocation);
registerSignalHandlers();
if (parser.isSet(debugOption))
nhlog::enable_debug_log_from_commandline = true;
try {
nhlog::init(QString("%1/nheko.log")
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
.toStdString());
} catch (const spdlog::spdlog_ex &ex) {
std::cout << "Log initialization failed: " << ex.what() << std::endl;
std::exit(1);
}
if (parser.isSet(configName))
UserSettings::initialize(parser.value(configName));
else
UserSettings::initialize(std::nullopt);
auto settings = UserSettings::instance().toWeakRef();
QFont font;
QString userFontFamily = settings.lock()->font();
if (!userFontFamily.isEmpty() && userFontFamily != "default") {
font.setFamily(userFontFamily);
// This is some hacky programming, but it's necessary (AFAIK?) to get the unique config name
// parsed before the SingleApplication userdata is set.
QString userdata{""};
QString matrixUri;
for (int i = 1; i < argc; ++i) {
QString arg{argv[i]};
if (arg.startsWith("--profile=")) {
arg.remove("--profile=");
userdata = arg;
} else if (arg.startsWith("--p=")) {
arg.remove("-p=");
userdata = arg;
} else if (arg == "--profile" || arg == "-p") {
if (i < argc - 1) // if i is less than argc - 1, we still have a parameter
// left to process as the name
{
++i; // the next arg is the name, so increment
userdata = QString{argv[i]};
}
} else if (arg.startsWith("matrix:")) {
matrixUri = arg;
}
font.setPointSizeF(settings.lock()->fontSize());
app.setFont(font);
QString lang = QLocale::system().name();
QTranslator qtTranslator;
qtTranslator.load(
QLocale(), "qt", "_", QLibraryInfo::location(QLibraryInfo::TranslationsPath));
app.installTranslator(&qtTranslator);
QTranslator appTranslator;
appTranslator.load(QLocale(), "nheko", "_", ":/translations");
app.installTranslator(&appTranslator);
MainWindow w;
// Move the MainWindow to the center
w.move(screenCenter(w.width(), w.height()));
if (!(settings.lock()->startInTray() && settings.lock()->tray()))
w.show();
QObject::connect(&app, &QApplication::aboutToQuit, &w, [&w]() {
w.saveCurrentWindowSize();
if (http::client() != nullptr) {
nhlog::net()->debug("shutting down all I/O threads & open connections");
http::client()->close(true);
nhlog::net()->debug("bye");
}
});
QObject::connect(&app, &SingleApplication::instanceStarted, &w, [&w]() {
w.show();
w.raise();
w.activateWindow();
});
QObject::connect(
&app,
&SingleApplication::receivedMessage,
ChatPage::instance(),
[&](quint32, QByteArray message) { ChatPage::instance()->handleMatrixUri(message); });
QMetaObject::Connection uriConnection;
if (app.isPrimary() && !matrixUri.isEmpty()) {
uriConnection = QObject::connect(ChatPage::instance(),
&ChatPage::contentLoaded,
ChatPage::instance(),
[&uriConnection, matrixUri]() {
ChatPage::instance()->handleMatrixUri(
matrixUri.toUtf8());
QObject::disconnect(uriConnection);
});
}
SingleApplication app(argc,
argv,
true,
SingleApplication::Mode::User | SingleApplication::Mode::ExcludeAppPath |
SingleApplication::Mode::ExcludeAppVersion |
SingleApplication::Mode::SecondaryNotification,
100,
userdata);
if (app.isSecondary()) {
// open uri in main instance
app.sendMessage(matrixUri.toUtf8());
return 0;
}
QCommandLineParser parser;
parser.addHelpOption();
parser.addVersionOption();
QCommandLineOption debugOption("debug", "Enable debug output");
parser.addOption(debugOption);
// This option is not actually parsed via Qt due to the need to parse it before the app
// name is set. It only exists to keep Qt from complaining about the --profile/-p
// option and thereby crashing the app.
QCommandLineOption configName(
QStringList() << "p"
<< "profile",
QCoreApplication::tr("Create a unique profile, which allows you to log into several "
"accounts at the same time and start multiple instances of nheko."),
QCoreApplication::tr("profile"),
QCoreApplication::tr("profile name"));
parser.addOption(configName);
parser.process(app);
app.setWindowIcon(QIcon::fromTheme("nheko", QIcon{":/logos/nheko.png"}));
http::init();
createStandardDirectory(QStandardPaths::CacheLocation);
createStandardDirectory(QStandardPaths::AppDataLocation);
registerSignalHandlers();
if (parser.isSet(debugOption))
nhlog::enable_debug_log_from_commandline = true;
try {
nhlog::init(QString("%1/nheko.log")
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))
.toStdString());
} catch (const spdlog::spdlog_ex &ex) {
std::cout << "Log initialization failed: " << ex.what() << std::endl;
std::exit(1);
}
if (parser.isSet(configName))
UserSettings::initialize(parser.value(configName));
else
UserSettings::initialize(std::nullopt);
auto settings = UserSettings::instance().toWeakRef();
QFont font;
QString userFontFamily = settings.lock()->font();
if (!userFontFamily.isEmpty() && userFontFamily != "default") {
font.setFamily(userFontFamily);
}
font.setPointSizeF(settings.lock()->fontSize());
app.setFont(font);
QString lang = QLocale::system().name();
QTranslator qtTranslator;
qtTranslator.load(QLocale(), "qt", "_", QLibraryInfo::location(QLibraryInfo::TranslationsPath));
app.installTranslator(&qtTranslator);
QTranslator appTranslator;
appTranslator.load(QLocale(), "nheko", "_", ":/translations");
app.installTranslator(&appTranslator);
MainWindow w;
// Move the MainWindow to the center
w.move(screenCenter(w.width(), w.height()));
if (!(settings.lock()->startInTray() && settings.lock()->tray()))
w.show();
QObject::connect(&app, &QApplication::aboutToQuit, &w, [&w]() {
w.saveCurrentWindowSize();
if (http::client() != nullptr) {
nhlog::net()->debug("shutting down all I/O threads & open connections");
http::client()->close(true);
nhlog::net()->debug("bye");
}
QDesktopServices::setUrlHandler("matrix", ChatPage::instance(), "handleMatrixUri");
});
QObject::connect(&app, &SingleApplication::instanceStarted, &w, [&w]() {
w.show();
w.raise();
w.activateWindow();
});
QObject::connect(
&app,
&SingleApplication::receivedMessage,
ChatPage::instance(),
[&](quint32, QByteArray message) { ChatPage::instance()->handleMatrixUri(message); });
QMetaObject::Connection uriConnection;
if (app.isPrimary() && !matrixUri.isEmpty()) {
uriConnection =
QObject::connect(ChatPage::instance(),
&ChatPage::contentLoaded,
ChatPage::instance(),
[&uriConnection, matrixUri]() {
ChatPage::instance()->handleMatrixUri(matrixUri.toUtf8());
QObject::disconnect(uriConnection);
});
}
QDesktopServices::setUrlHandler("matrix", ChatPage::instance(), "handleMatrixUri");
#if defined(Q_OS_MAC)
// Temporary solution for the emoji picker until
// nheko has a proper menu bar with more functionality.
MacHelper::initializeMenus();
// Temporary solution for the emoji picker until
// nheko has a proper menu bar with more functionality.
MacHelper::initializeMenus();
#endif
nhlog::ui()->info("starting nheko {}", nheko::version);
nhlog::ui()->info("starting nheko {}", nheko::version);
return app.exec();
return app.exec();
}

@ -11,30 +11,30 @@
QString
NotificationsManager::getMessageTemplate(const mtx::responses::Notification &notification)
{
const auto sender =
cache::displayName(QString::fromStdString(notification.room_id),
QString::fromStdString(mtx::accessors::sender(notification.event)));
const auto sender =
cache::displayName(QString::fromStdString(notification.room_id),
QString::fromStdString(mtx::accessors::sender(notification.event)));
// TODO: decrypt this message if the decryption setting is on in the UserSettings
if (auto msg = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
&notification.event);
msg != nullptr) {
return tr("%1 sent an encrypted message").arg(sender);
}
// TODO: decrypt this message if the decryption setting is on in the UserSettings
if (auto msg = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
&notification.event);
msg != nullptr) {
return tr("%1 sent an encrypted message").arg(sender);
}
if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) {
return tr("* %1 %2",
"Format an emote message in a notification, %1 is the sender, %2 the "
"message")
.arg(sender);
} else if (utils::isReply(notification.event)) {
return tr("%1 replied: %2",
"Format a reply in a notification. %1 is the sender, %2 the message")
.arg(sender);
} else {
return tr("%1: %2",
"Format a normal message in a notification. %1 is the sender, %2 the "
"message")
.arg(sender);
}
if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Emote) {
return tr("* %1 %2",
"Format an emote message in a notification, %1 is the sender, %2 the "
"message")
.arg(sender);
} else if (utils::isReply(notification.event)) {
return tr("%1 replied: %2",
"Format a reply in a notification. %1 is the sender, %2 the message")
.arg(sender);
} else {
return tr("%1: %2",
"Format a normal message in a notification. %1 is the sender, %2 the "
"message")
.arg(sender);
}
}

@ -22,83 +22,83 @@
struct roomEventId
{
QString roomId;
QString eventId;
QString roomId;
QString eventId;
};
inline bool
operator==(const roomEventId &a, const roomEventId &b)
{
return a.roomId == b.roomId && a.eventId == b.eventId;
return a.roomId == b.roomId && a.eventId == b.eventId;
}
class NotificationsManager : public QObject
{
Q_OBJECT
Q_OBJECT
public:
NotificationsManager(QObject *parent = nullptr);
NotificationsManager(QObject *parent = nullptr);
void postNotification(const mtx::responses::Notification &notification, const QImage &icon);
void postNotification(const mtx::responses::Notification &notification, const QImage &icon);
signals:
void notificationClicked(const QString roomId, const QString eventId);
void sendNotificationReply(const QString roomId, const QString eventId, const QString body);
void systemPostNotificationCb(const QString &room_id,
const QString &event_id,
const QString &roomName,
const QString &text,
const QImage &icon);
void notificationClicked(const QString roomId, const QString eventId);
void sendNotificationReply(const QString roomId, const QString eventId, const QString body);
void systemPostNotificationCb(const QString &room_id,
const QString &event_id,
const QString &roomName,
const QString &text,
const QImage &icon);
public slots:
void removeNotification(const QString &roomId, const QString &eventId);
void removeNotification(const QString &roomId, const QString &eventId);
#if defined(NHEKO_DBUS_SYS)
public:
void closeNotifications(QString roomId);
void closeNotifications(QString roomId);
private:
QDBusInterface dbus;
QDBusInterface dbus;
void systemPostNotification(const QString &room_id,
const QString &event_id,
const QString &roomName,
const QString &text,
const QImage &icon);
void closeNotification(uint id);
void systemPostNotification(const QString &room_id,
const QString &event_id,
const QString &roomName,
const QString &text,
const QImage &icon);
void closeNotification(uint id);
// notification ID to (room ID, event ID)
QMap<uint, roomEventId> notificationIds;
// notification ID to (room ID, event ID)
QMap<uint, roomEventId> notificationIds;
const bool hasMarkup_;
const bool hasImages_;
const bool hasMarkup_;
const bool hasImages_;
#endif
#if defined(Q_OS_MACOS)
private:
// Objective-C(++) doesn't like to do lots of regular C++, so the actual notification
// posting is split out
void objCxxPostNotification(const QString &title,
const QString &subtitle,
const QString &informativeText,
const QImage &bodyImage);
// Objective-C(++) doesn't like to do lots of regular C++, so the actual notification
// posting is split out
void objCxxPostNotification(const QString &title,
const QString &subtitle,
const QString &informativeText,
const QImage &bodyImage);
#endif
#if defined(Q_OS_WINDOWS)
private:
void systemPostNotification(const QString &line1,
const QString &line2,
const QString &iconPath);
void systemPostNotification(const QString &line1,
const QString &line2,
const QString &iconPath);
#endif
// these slots are platform specific (D-Bus only)
// but Qt slot declarations can not be inside an ifdef!
// these slots are platform specific (D-Bus only)
// but Qt slot declarations can not be inside an ifdef!
private slots:
void actionInvoked(uint id, QString action);
void notificationClosed(uint id, uint reason);
void notificationReplied(uint id, QString reply);
void actionInvoked(uint id, QString action);
void notificationClosed(uint id, uint reason);
void notificationReplied(uint id, QString reply);
private:
QString getMessageTemplate(const mtx::responses::Notification &notification);
QString getMessageTemplate(const mtx::responses::Notification &notification);
};
#if defined(NHEKO_DBUS_SYS)

@ -34,109 +34,100 @@ NotificationsManager::NotificationsManager(QObject *parent)
QDBusConnection::sessionBus(),
this)
, hasMarkup_{std::invoke([this]() -> bool {
for (auto x : dbus.call("GetCapabilities").arguments())
if (x.toStringList().contains("body-markup"))
return true;
return false;
for (auto x : dbus.call("GetCapabilities").arguments())
if (x.toStringList().contains("body-markup"))
return true;
return false;
})}
, hasImages_{std::invoke([this]() -> bool {
for (auto x : dbus.call("GetCapabilities").arguments())
if (x.toStringList().contains("body-images"))
return true;
return false;
for (auto x : dbus.call("GetCapabilities").arguments())
if (x.toStringList().contains("body-images"))
return true;
return false;
})}
{
qDBusRegisterMetaType<QImage>();
qDBusRegisterMetaType<QImage>();
QDBusConnection::sessionBus().connect("org.freedesktop.Notifications",
"/org/freedesktop/Notifications",
"org.freedesktop.Notifications",
"ActionInvoked",
this,
SLOT(actionInvoked(uint, QString)));
QDBusConnection::sessionBus().connect("org.freedesktop.Notifications",
"/org/freedesktop/Notifications",
"org.freedesktop.Notifications",
"NotificationClosed",
this,
SLOT(notificationClosed(uint, uint)));
QDBusConnection::sessionBus().connect("org.freedesktop.Notifications",
"/org/freedesktop/Notifications",
"org.freedesktop.Notifications",
"NotificationReplied",
this,
SLOT(notificationReplied(uint, QString)));
QDBusConnection::sessionBus().connect("org.freedesktop.Notifications",
"/org/freedesktop/Notifications",
"org.freedesktop.Notifications",
"ActionInvoked",
this,
SLOT(actionInvoked(uint, QString)));
QDBusConnection::sessionBus().connect("org.freedesktop.Notifications",
"/org/freedesktop/Notifications",
"org.freedesktop.Notifications",
"NotificationClosed",
this,
SLOT(notificationClosed(uint, uint)));
QDBusConnection::sessionBus().connect("org.freedesktop.Notifications",
"/org/freedesktop/Notifications",
"org.freedesktop.Notifications",
"NotificationReplied",
this,
SLOT(notificationReplied(uint, QString)));
connect(this,
&NotificationsManager::systemPostNotificationCb,
this,
&NotificationsManager::systemPostNotification,
Qt::QueuedConnection);
connect(this,
&NotificationsManager::systemPostNotificationCb,
this,
&NotificationsManager::systemPostNotification,
Qt::QueuedConnection);
}
void
NotificationsManager::postNotification(const mtx::responses::Notification &notification,
const QImage &icon)
{
const auto room_id = QString::fromStdString(notification.room_id);
const auto event_id = QString::fromStdString(mtx::accessors::event_id(notification.event));
const auto room_name =
QString::fromStdString(cache::singleRoomInfo(notification.room_id).name);
const auto room_id = QString::fromStdString(notification.room_id);
const auto event_id = QString::fromStdString(mtx::accessors::event_id(notification.event));
const auto room_name = QString::fromStdString(cache::singleRoomInfo(notification.room_id).name);
auto postNotif = [this, room_id, event_id, room_name, icon](QString text) {
emit systemPostNotificationCb(room_id, event_id, room_name, text, icon);
};
QString template_ = getMessageTemplate(notification);
// TODO: decrypt this message if the decryption setting is on in the UserSettings
if (std::holds_alternative<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
notification.event)) {
postNotif(template_);
return;
}
auto postNotif = [this, room_id, event_id, room_name, icon](QString text) {
emit systemPostNotificationCb(room_id, event_id, room_name, text, icon);
};
if (hasMarkup_) {
if (hasImages_ && mtx::accessors::msg_type(notification.event) ==
mtx::events::MessageType::Image) {
MxcImageProvider::download(
QString::fromStdString(mtx::accessors::url(notification.event))
.remove("mxc://"),
QSize(200, 80),
[postNotif, notification, template_](
QString, QSize, QImage, QString imgPath) {
if (imgPath.isEmpty())
postNotif(template_
.arg(utils::stripReplyFallbacks(
notification.event, {}, {})
.quoted_formatted_body)
.replace("<em>", "<i>")
.replace("</em>", "</i>")
.replace("<strong>", "<b>")
.replace("</strong>", "</b>"));
else
postNotif(template_.arg(
QStringLiteral("<br><img src=\"file:///") % imgPath %
"\" alt=\"" %
mtx::accessors::formattedBodyWithFallback(
notification.event) %
"\">"));
});
return;
}
QString template_ = getMessageTemplate(notification);
// TODO: decrypt this message if the decryption setting is on in the UserSettings
if (std::holds_alternative<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
notification.event)) {
postNotif(template_);
return;
}
postNotif(
template_
.arg(
utils::stripReplyFallbacks(notification.event, {}, {}).quoted_formatted_body)
.replace("<em>", "<i>")
.replace("</em>", "</i>")
.replace("<strong>", "<b>")
.replace("</strong>", "</b>"));
return;
if (hasMarkup_) {
if (hasImages_ &&
mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image) {
MxcImageProvider::download(
QString::fromStdString(mtx::accessors::url(notification.event)).remove("mxc://"),
QSize(200, 80),
[postNotif, notification, template_](QString, QSize, QImage, QString imgPath) {
if (imgPath.isEmpty())
postNotif(template_
.arg(utils::stripReplyFallbacks(notification.event, {}, {})
.quoted_formatted_body)
.replace("<em>", "<i>")
.replace("</em>", "</i>")
.replace("<strong>", "<b>")
.replace("</strong>", "</b>"));
else
postNotif(template_.arg(
QStringLiteral("<br><img src=\"file:///") % imgPath % "\" alt=\"" %
mtx::accessors::formattedBodyWithFallback(notification.event) % "\">"));
});
return;
}
postNotif(
template_.arg(utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body));
template_
.arg(utils::stripReplyFallbacks(notification.event, {}, {}).quoted_formatted_body)
.replace("<em>", "<i>")
.replace("</em>", "</i>")
.replace("<strong>", "<b>")
.replace("</strong>", "</b>"));
return;
}
postNotif(template_.arg(utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body));
}
/**
@ -152,99 +143,99 @@ NotificationsManager::systemPostNotification(const QString &room_id,
const QString &text,
const QImage &icon)
{
QVariantMap hints;
hints["image-data"] = icon;
hints["sound-name"] = "message-new-instant";
QList<QVariant> argumentList;
argumentList << "nheko"; // app_name
argumentList << (uint)0; // replace_id
argumentList << ""; // app_icon
argumentList << roomName; // summary
argumentList << text; // body
QVariantMap hints;
hints["image-data"] = icon;
hints["sound-name"] = "message-new-instant";
QList<QVariant> argumentList;
argumentList << "nheko"; // app_name
argumentList << (uint)0; // replace_id
argumentList << ""; // app_icon
argumentList << roomName; // summary
argumentList << text; // body
// The list of actions has always the action name and then a localized version of that
// action. Currently we just use an empty string for that.
// TODO(Nico): Look into what to actually put there.
argumentList << (QStringList("default") << ""
<< "inline-reply"
<< ""); // actions
argumentList << hints; // hints
argumentList << (int)-1; // timeout in ms
// The list of actions has always the action name and then a localized version of that
// action. Currently we just use an empty string for that.
// TODO(Nico): Look into what to actually put there.
argumentList << (QStringList("default") << ""
<< "inline-reply"
<< ""); // actions
argumentList << hints; // hints
argumentList << (int)-1; // timeout in ms
QDBusPendingCall call = dbus.asyncCallWithArgumentList("Notify", argumentList);
auto watcher = new QDBusPendingCallWatcher{call, this};
connect(
watcher, &QDBusPendingCallWatcher::finished, this, [watcher, this, room_id, event_id]() {
if (watcher->reply().type() == QDBusMessage::ErrorMessage)
qDebug() << "D-Bus Error:" << watcher->reply().errorMessage();
else
notificationIds[watcher->reply().arguments().first().toUInt()] =
roomEventId{room_id, event_id};
watcher->deleteLater();
});
QDBusPendingCall call = dbus.asyncCallWithArgumentList("Notify", argumentList);
auto watcher = new QDBusPendingCallWatcher{call, this};
connect(
watcher, &QDBusPendingCallWatcher::finished, this, [watcher, this, room_id, event_id]() {
if (watcher->reply().type() == QDBusMessage::ErrorMessage)
qDebug() << "D-Bus Error:" << watcher->reply().errorMessage();
else
notificationIds[watcher->reply().arguments().first().toUInt()] =
roomEventId{room_id, event_id};
watcher->deleteLater();
});
}
void
NotificationsManager::closeNotification(uint id)
{
auto call = dbus.asyncCall("CloseNotification", (uint)id); // replace_id
auto watcher = new QDBusPendingCallWatcher{call, this};
connect(watcher, &QDBusPendingCallWatcher::finished, this, [watcher]() {
if (watcher->reply().type() == QDBusMessage::ErrorMessage) {
qDebug() << "D-Bus Error:" << watcher->reply().errorMessage();
};
watcher->deleteLater();
});
auto call = dbus.asyncCall("CloseNotification", (uint)id); // replace_id
auto watcher = new QDBusPendingCallWatcher{call, this};
connect(watcher, &QDBusPendingCallWatcher::finished, this, [watcher]() {
if (watcher->reply().type() == QDBusMessage::ErrorMessage) {
qDebug() << "D-Bus Error:" << watcher->reply().errorMessage();
};
watcher->deleteLater();
});
}
void
NotificationsManager::removeNotification(const QString &roomId, const QString &eventId)
{
roomEventId reId = {roomId, eventId};
for (auto elem = notificationIds.begin(); elem != notificationIds.end(); ++elem) {
if (elem.value().roomId != roomId)
continue;
roomEventId reId = {roomId, eventId};
for (auto elem = notificationIds.begin(); elem != notificationIds.end(); ++elem) {
if (elem.value().roomId != roomId)
continue;
// close all notifications matching the eventId or having a lower
// notificationId
// This relies on the notificationId not wrapping around. This allows for
// approximately 2,147,483,647 notifications, so it is a bit unlikely.
// Otherwise we would need to store a 64bit counter instead.
closeNotification(elem.key());
// close all notifications matching the eventId or having a lower
// notificationId
// This relies on the notificationId not wrapping around. This allows for
// approximately 2,147,483,647 notifications, so it is a bit unlikely.
// Otherwise we would need to store a 64bit counter instead.
closeNotification(elem.key());
// FIXME: compare index of event id of the read receipt and the notification instead
// of just the id to prevent read receipts of events without notification clearing
// all notifications in that room!
if (elem.value() == reId)
break;
}
// FIXME: compare index of event id of the read receipt and the notification instead
// of just the id to prevent read receipts of events without notification clearing
// all notifications in that room!
if (elem.value() == reId)
break;
}
}
void
NotificationsManager::actionInvoked(uint id, QString action)
{
if (notificationIds.contains(id)) {
roomEventId idEntry = notificationIds[id];
if (action == "default") {
emit notificationClicked(idEntry.roomId, idEntry.eventId);
}
if (notificationIds.contains(id)) {
roomEventId idEntry = notificationIds[id];
if (action == "default") {
emit notificationClicked(idEntry.roomId, idEntry.eventId);
}
}
}
void
NotificationsManager::notificationReplied(uint id, QString reply)
{
if (notificationIds.contains(id)) {
roomEventId idEntry = notificationIds[id];
emit sendNotificationReply(idEntry.roomId, idEntry.eventId, reply);
}
if (notificationIds.contains(id)) {
roomEventId idEntry = notificationIds[id];
emit sendNotificationReply(idEntry.roomId, idEntry.eventId, reply);
}
}
void
NotificationsManager::notificationClosed(uint id, uint reason)
{
Q_UNUSED(reason);
notificationIds.remove(id);
Q_UNUSED(reason);
notificationIds.remove(id);
}
/**
@ -259,52 +250,52 @@ NotificationsManager::notificationClosed(uint id, uint reason)
QDBusArgument &
operator<<(QDBusArgument &arg, const QImage &image)
{
if (image.isNull()) {
arg.beginStructure();
arg << 0 << 0 << 0 << false << 0 << 0 << QByteArray();
arg.endStructure();
return arg;
}
if (image.isNull()) {
arg.beginStructure();
arg << 0 << 0 << 0 << false << 0 << 0 << QByteArray();
arg.endStructure();
return arg;
}
QImage scaled = image.scaledToHeight(100, Qt::SmoothTransformation);
scaled = scaled.convertToFormat(QImage::Format_ARGB32);
QImage scaled = image.scaledToHeight(100, Qt::SmoothTransformation);
scaled = scaled.convertToFormat(QImage::Format_ARGB32);
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
// ABGR -> ARGB
QImage i = scaled.rgbSwapped();
// ABGR -> ARGB
QImage i = scaled.rgbSwapped();
#else
// ABGR -> GBAR
QImage i(scaled.size(), scaled.format());
for (int y = 0; y < i.height(); ++y) {
QRgb *p = (QRgb *)scaled.scanLine(y);
QRgb *q = (QRgb *)i.scanLine(y);
QRgb *end = p + scaled.width();
while (p < end) {
*q = qRgba(qGreen(*p), qBlue(*p), qAlpha(*p), qRed(*p));
p++;
q++;
}
// ABGR -> GBAR
QImage i(scaled.size(), scaled.format());
for (int y = 0; y < i.height(); ++y) {
QRgb *p = (QRgb *)scaled.scanLine(y);
QRgb *q = (QRgb *)i.scanLine(y);
QRgb *end = p + scaled.width();
while (p < end) {
*q = qRgba(qGreen(*p), qBlue(*p), qAlpha(*p), qRed(*p));
p++;
q++;
}
}
#endif
arg.beginStructure();
arg << i.width();
arg << i.height();
arg << i.bytesPerLine();
arg << i.hasAlphaChannel();
int channels = i.isGrayscale() ? 1 : (i.hasAlphaChannel() ? 4 : 3);
arg << i.depth() / channels;
arg << channels;
arg << QByteArray(reinterpret_cast<const char *>(i.bits()), i.sizeInBytes());
arg.endStructure();
arg.beginStructure();
arg << i.width();
arg << i.height();
arg << i.bytesPerLine();
arg << i.hasAlphaChannel();
int channels = i.isGrayscale() ? 1 : (i.hasAlphaChannel() ? 4 : 3);
arg << i.depth() / channels;
arg << channels;
arg << QByteArray(reinterpret_cast<const char *>(i.bits()), i.sizeInBytes());
arg.endStructure();
return arg;
return arg;
}
const QDBusArgument &
operator>>(const QDBusArgument &arg, QImage &)
{
// This is needed to link but shouldn't be called.
Q_ASSERT(0);
return arg;
// This is needed to link but shouldn't be called.
Q_ASSERT(0);
return arg;
}

@ -19,48 +19,42 @@
static QString
formatNotification(const mtx::responses::Notification &notification)
{
return utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body;
return utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body;
}
void
NotificationsManager::postNotification(const mtx::responses::Notification &notification,
const QImage &icon)
{
Q_UNUSED(icon)
const auto room_name =
QString::fromStdString(cache::singleRoomInfo(notification.room_id).name);
const auto sender =
cache::displayName(QString::fromStdString(notification.room_id),
QString::fromStdString(mtx::accessors::sender(notification.event)));
const auto isEncrypted =
std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
&notification.event) != nullptr;
const auto isReply = utils::isReply(notification.event);
if (isEncrypted) {
// TODO: decrypt this message if the decryption setting is on in the UserSettings
const QString messageInfo = (isReply ? tr("%1 replied with an encrypted message")
: tr("%1 sent an encrypted message"))
.arg(sender);
objCxxPostNotification(room_name, messageInfo, "", QImage());
} else {
const QString messageInfo =
(isReply ? tr("%1 replied to a message") : tr("%1 sent a message")).arg(sender);
if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image)
MxcImageProvider::download(
QString::fromStdString(mtx::accessors::url(notification.event))
.remove("mxc://"),
QSize(200, 80),
[this, notification, room_name, messageInfo](
QString, QSize, QImage image, QString) {
objCxxPostNotification(room_name,
messageInfo,
formatNotification(notification),
image);
});
else
objCxxPostNotification(
room_name, messageInfo, formatNotification(notification), QImage());
}
Q_UNUSED(icon)
const auto room_name = QString::fromStdString(cache::singleRoomInfo(notification.room_id).name);
const auto sender =
cache::displayName(QString::fromStdString(notification.room_id),
QString::fromStdString(mtx::accessors::sender(notification.event)));
const auto isEncrypted = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
&notification.event) != nullptr;
const auto isReply = utils::isReply(notification.event);
if (isEncrypted) {
// TODO: decrypt this message if the decryption setting is on in the UserSettings
const QString messageInfo = (isReply ? tr("%1 replied with an encrypted message")
: tr("%1 sent an encrypted message"))
.arg(sender);
objCxxPostNotification(room_name, messageInfo, "", QImage());
} else {
const QString messageInfo =
(isReply ? tr("%1 replied to a message") : tr("%1 sent a message")).arg(sender);
if (mtx::accessors::msg_type(notification.event) == mtx::events::MessageType::Image)
MxcImageProvider::download(
QString::fromStdString(mtx::accessors::url(notification.event)).remove("mxc://"),
QSize(200, 80),
[this, notification, room_name, messageInfo](QString, QSize, QImage image, QString) {
objCxxPostNotification(
room_name, messageInfo, formatNotification(notification), image);
});
else
objCxxPostNotification(
room_name, messageInfo, formatNotification(notification), QImage());
}
}

@ -20,10 +20,10 @@ using namespace WinToastLib;
class CustomHandler : public IWinToastHandler
{
public:
void toastActivated() const {}
void toastActivated(int) const {}
void toastFailed() const { std::wcout << L"Error showing current toast" << std::endl; }
void toastDismissed(WinToastDismissalReason) const {}
void toastActivated() const {}
void toastActivated(int) const {}
void toastFailed() const { std::wcout << L"Error showing current toast" << std::endl; }
void toastDismissed(WinToastDismissalReason) const {}
};
namespace {
@ -32,12 +32,12 @@ bool isInitialized = false;
void
init()
{
isInitialized = true;
isInitialized = true;
WinToast::instance()->setAppName(L"Nheko");
WinToast::instance()->setAppUserModelId(WinToast::configureAUMI(L"nheko", L"nheko"));
if (!WinToast::instance()->initialize())
std::wcout << "Your system is not compatible with toast notifications\n";
WinToast::instance()->setAppName(L"Nheko");
WinToast::instance()->setAppUserModelId(WinToast::configureAUMI(L"nheko", L"nheko"));
if (!WinToast::instance()->initialize())
std::wcout << "Your system is not compatible with toast notifications\n";
}
}
@ -49,41 +49,37 @@ void
NotificationsManager::postNotification(const mtx::responses::Notification &notification,
const QImage &icon)
{
const auto room_name =
QString::fromStdString(cache::singleRoomInfo(notification.room_id).name);
const auto sender =
cache::displayName(QString::fromStdString(notification.room_id),
QString::fromStdString(mtx::accessors::sender(notification.event)));
const auto isEncrypted =
std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
&notification.event) != nullptr;
const auto isReply = utils::isReply(notification.event);
auto formatNotification = [this, notification, sender] {
const auto template_ = getMessageTemplate(notification);
if (std::holds_alternative<
mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
notification.event)) {
return template_;
}
return template_.arg(
utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body);
};
const auto line1 =
(room_name == sender) ? sender : QString("%1 - %2").arg(sender).arg(room_name);
const auto line2 = (isEncrypted ? (isReply ? tr("%1 replied with an encrypted message")
: tr("%1 sent an encrypted message"))
: formatNotification());
auto iconPath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) +
room_name + "-room-avatar.png";
if (!icon.save(iconPath))
iconPath.clear();
systemPostNotification(line1, line2, iconPath);
const auto room_name = QString::fromStdString(cache::singleRoomInfo(notification.room_id).name);
const auto sender =
cache::displayName(QString::fromStdString(notification.room_id),
QString::fromStdString(mtx::accessors::sender(notification.event)));
const auto isEncrypted = std::get_if<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
&notification.event) != nullptr;
const auto isReply = utils::isReply(notification.event);
auto formatNotification = [this, notification, sender] {
const auto template_ = getMessageTemplate(notification);
if (std::holds_alternative<mtx::events::EncryptedEvent<mtx::events::msg::Encrypted>>(
notification.event)) {
return template_;
}
return template_.arg(utils::stripReplyFallbacks(notification.event, {}, {}).quoted_body);
};
const auto line1 =
(room_name == sender) ? sender : QString("%1 - %2").arg(sender).arg(room_name);
const auto line2 = (isEncrypted ? (isReply ? tr("%1 replied with an encrypted message")
: tr("%1 sent an encrypted message"))
: formatNotification());
auto iconPath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + room_name +
"-room-avatar.png";
if (!icon.save(iconPath))
iconPath.clear();
systemPostNotification(line1, line2, iconPath);
}
void
@ -91,17 +87,17 @@ NotificationsManager::systemPostNotification(const QString &line1,
const QString &line2,
const QString &iconPath)
{
if (!isInitialized)
init();
if (!isInitialized)
init();
auto templ = WinToastTemplate(WinToastTemplate::ImageAndText02);
templ.setTextField(line1.toStdWString(), WinToastTemplate::FirstLine);
templ.setTextField(line2.toStdWString(), WinToastTemplate::SecondLine);
auto templ = WinToastTemplate(WinToastTemplate::ImageAndText02);
templ.setTextField(line1.toStdWString(), WinToastTemplate::FirstLine);
templ.setTextField(line2.toStdWString(), WinToastTemplate::SecondLine);
if (!iconPath.isNull())
templ.setImagePath(iconPath.toStdWString());
if (!iconPath.isNull())
templ.setImagePath(iconPath.toStdWString());
WinToast::instance()->showToast(templ, new CustomHandler());
WinToast::instance()->showToast(templ, new CustomHandler());
}
void NotificationsManager::actionInvoked(uint, QString) {}

@ -16,231 +16,230 @@ CommunitiesModel::CommunitiesModel(QObject *parent)
QHash<int, QByteArray>
CommunitiesModel::roleNames() const
{
return {
{AvatarUrl, "avatarUrl"},
{DisplayName, "displayName"},
{Tooltip, "tooltip"},
{ChildrenHidden, "childrenHidden"},
{Hidden, "hidden"},
{Id, "id"},
};
return {
{AvatarUrl, "avatarUrl"},
{DisplayName, "displayName"},
{Tooltip, "tooltip"},
{ChildrenHidden, "childrenHidden"},
{Hidden, "hidden"},
{Id, "id"},
};
}
QVariant
CommunitiesModel::data(const QModelIndex &index, int role) const
{
if (index.row() == 0) {
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QString(":/icons/icons/ui/world.png");
case CommunitiesModel::Roles::DisplayName:
return tr("All rooms");
case CommunitiesModel::Roles::Tooltip:
return tr("Shows all rooms without filtering.");
case CommunitiesModel::Roles::ChildrenHidden:
return false;
case CommunitiesModel::Roles::Hidden:
return false;
case CommunitiesModel::Roles::Id:
return "";
}
} else if (index.row() - 1 < spaceOrder_.size()) {
auto id = spaceOrder_.at(index.row() - 1);
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QString::fromStdString(spaces_.at(id).avatar_url);
case CommunitiesModel::Roles::DisplayName:
case CommunitiesModel::Roles::Tooltip:
return QString::fromStdString(spaces_.at(id).name);
case CommunitiesModel::Roles::ChildrenHidden:
return true;
case CommunitiesModel::Roles::Hidden:
return hiddentTagIds_.contains("space:" + id);
case CommunitiesModel::Roles::Id:
return "space:" + id;
}
} else if (index.row() - 1 < tags_.size() + spaceOrder_.size()) {
auto tag = tags_.at(index.row() - 1 - spaceOrder_.size());
if (tag == "m.favourite") {
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QString(":/icons/icons/ui/star.png");
case CommunitiesModel::Roles::DisplayName:
return tr("Favourites");
case CommunitiesModel::Roles::Tooltip:
return tr("Rooms you have favourited.");
}
} else if (tag == "m.lowpriority") {
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QString(":/icons/icons/ui/lowprio.png");
case CommunitiesModel::Roles::DisplayName:
return tr("Low Priority");
case CommunitiesModel::Roles::Tooltip:
return tr("Rooms with low priority.");
}
} else if (tag == "m.server_notice") {
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QString(":/icons/icons/ui/tag.png");
case CommunitiesModel::Roles::DisplayName:
return tr("Server Notices");
case CommunitiesModel::Roles::Tooltip:
return tr("Messages from your server or administrator.");
}
} else {
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QString(":/icons/icons/ui/tag.png");
case CommunitiesModel::Roles::DisplayName:
case CommunitiesModel::Roles::Tooltip:
return tag.mid(2);
}
}
if (index.row() == 0) {
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QString(":/icons/icons/ui/world.png");
case CommunitiesModel::Roles::DisplayName:
return tr("All rooms");
case CommunitiesModel::Roles::Tooltip:
return tr("Shows all rooms without filtering.");
case CommunitiesModel::Roles::ChildrenHidden:
return false;
case CommunitiesModel::Roles::Hidden:
return false;
case CommunitiesModel::Roles::Id:
return "";
}
} else if (index.row() - 1 < spaceOrder_.size()) {
auto id = spaceOrder_.at(index.row() - 1);
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QString::fromStdString(spaces_.at(id).avatar_url);
case CommunitiesModel::Roles::DisplayName:
case CommunitiesModel::Roles::Tooltip:
return QString::fromStdString(spaces_.at(id).name);
case CommunitiesModel::Roles::ChildrenHidden:
return true;
case CommunitiesModel::Roles::Hidden:
return hiddentTagIds_.contains("space:" + id);
case CommunitiesModel::Roles::Id:
return "space:" + id;
}
} else if (index.row() - 1 < tags_.size() + spaceOrder_.size()) {
auto tag = tags_.at(index.row() - 1 - spaceOrder_.size());
if (tag == "m.favourite") {
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QString(":/icons/icons/ui/star.png");
case CommunitiesModel::Roles::DisplayName:
return tr("Favourites");
case CommunitiesModel::Roles::Tooltip:
return tr("Rooms you have favourited.");
}
} else if (tag == "m.lowpriority") {
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QString(":/icons/icons/ui/lowprio.png");
case CommunitiesModel::Roles::DisplayName:
return tr("Low Priority");
case CommunitiesModel::Roles::Tooltip:
return tr("Rooms with low priority.");
}
} else if (tag == "m.server_notice") {
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QString(":/icons/icons/ui/tag.png");
case CommunitiesModel::Roles::DisplayName:
return tr("Server Notices");
case CommunitiesModel::Roles::Tooltip:
return tr("Messages from your server or administrator.");
}
} else {
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QString(":/icons/icons/ui/tag.png");
case CommunitiesModel::Roles::DisplayName:
case CommunitiesModel::Roles::Tooltip:
return tag.mid(2);
}
}
switch (role) {
case CommunitiesModel::Roles::Hidden:
return hiddentTagIds_.contains("tag:" + tag);
case CommunitiesModel::Roles::ChildrenHidden:
return true;
case CommunitiesModel::Roles::Id:
return "tag:" + tag;
}
switch (role) {
case CommunitiesModel::Roles::Hidden:
return hiddentTagIds_.contains("tag:" + tag);
case CommunitiesModel::Roles::ChildrenHidden:
return true;
case CommunitiesModel::Roles::Id:
return "tag:" + tag;
}
return QVariant();
}
return QVariant();
}
void
CommunitiesModel::initializeSidebar()
{
beginResetModel();
tags_.clear();
spaceOrder_.clear();
spaces_.clear();
std::set<std::string> ts;
std::vector<RoomInfo> tempSpaces;
auto infos = cache::roomInfo();
for (auto it = infos.begin(); it != infos.end(); it++) {
if (it.value().is_space) {
spaceOrder_.push_back(it.key());
spaces_[it.key()] = it.value();
} else {
for (const auto &t : it.value().tags) {
if (t.find("u.") == 0 || t.find("m." == 0)) {
ts.insert(t);
}
}
beginResetModel();
tags_.clear();
spaceOrder_.clear();
spaces_.clear();
std::set<std::string> ts;
std::vector<RoomInfo> tempSpaces;
auto infos = cache::roomInfo();
for (auto it = infos.begin(); it != infos.end(); it++) {
if (it.value().is_space) {
spaceOrder_.push_back(it.key());
spaces_[it.key()] = it.value();
} else {
for (const auto &t : it.value().tags) {
if (t.find("u.") == 0 || t.find("m." == 0)) {
ts.insert(t);
}
}
}
}
for (const auto &t : ts)
tags_.push_back(QString::fromStdString(t));
for (const auto &t : ts)
tags_.push_back(QString::fromStdString(t));
hiddentTagIds_ = UserSettings::instance()->hiddenTags();
endResetModel();
hiddentTagIds_ = UserSettings::instance()->hiddenTags();
endResetModel();
emit tagsChanged();
emit hiddenTagsChanged();
emit tagsChanged();
emit hiddenTagsChanged();
}
void
CommunitiesModel::clear()
{
beginResetModel();
tags_.clear();
endResetModel();
resetCurrentTagId();
beginResetModel();
tags_.clear();
endResetModel();
resetCurrentTagId();
emit tagsChanged();
emit tagsChanged();
}
void
CommunitiesModel::sync(const mtx::responses::Rooms &rooms)
{
bool tagsUpdated = false;
for (const auto &[roomid, room] : rooms.join) {
(void)roomid;
for (const auto &e : room.account_data.events)
if (std::holds_alternative<
mtx::events::AccountDataEvent<mtx::events::account_data::Tags>>(e)) {
tagsUpdated = true;
}
for (const auto &e : room.state.events)
if (std::holds_alternative<
mtx::events::StateEvent<mtx::events::state::space::Child>>(e) ||
std::holds_alternative<
mtx::events::StateEvent<mtx::events::state::space::Parent>>(e)) {
tagsUpdated = true;
}
for (const auto &e : room.timeline.events)
if (std::holds_alternative<
mtx::events::StateEvent<mtx::events::state::space::Child>>(e) ||
std::holds_alternative<
mtx::events::StateEvent<mtx::events::state::space::Parent>>(e)) {
tagsUpdated = true;
}
}
for (const auto &[roomid, room] : rooms.leave) {
(void)room;
if (spaceOrder_.contains(QString::fromStdString(roomid)))
tagsUpdated = true;
}
if (tagsUpdated)
initializeSidebar();
bool tagsUpdated = false;
for (const auto &[roomid, room] : rooms.join) {
(void)roomid;
for (const auto &e : room.account_data.events)
if (std::holds_alternative<
mtx::events::AccountDataEvent<mtx::events::account_data::Tags>>(e)) {
tagsUpdated = true;
}
for (const auto &e : room.state.events)
if (std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Child>>(
e) ||
std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Parent>>(
e)) {
tagsUpdated = true;
}
for (const auto &e : room.timeline.events)
if (std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Child>>(
e) ||
std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Parent>>(
e)) {
tagsUpdated = true;
}
}
for (const auto &[roomid, room] : rooms.leave) {
(void)room;
if (spaceOrder_.contains(QString::fromStdString(roomid)))
tagsUpdated = true;
}
if (tagsUpdated)
initializeSidebar();
}
void
CommunitiesModel::setCurrentTagId(QString tagId)
{
if (tagId.startsWith("tag:")) {
auto tag = tagId.mid(4);
for (const auto &t : tags_) {
if (t == tag) {
this->currentTagId_ = tagId;
emit currentTagIdChanged(currentTagId_);
return;
}
}
} else if (tagId.startsWith("space:")) {
auto tag = tagId.mid(6);
for (const auto &t : spaceOrder_) {
if (t == tag) {
this->currentTagId_ = tagId;
emit currentTagIdChanged(currentTagId_);
return;
}
}
if (tagId.startsWith("tag:")) {
auto tag = tagId.mid(4);
for (const auto &t : tags_) {
if (t == tag) {
this->currentTagId_ = tagId;
emit currentTagIdChanged(currentTagId_);
return;
}
}
} else if (tagId.startsWith("space:")) {
auto tag = tagId.mid(6);
for (const auto &t : spaceOrder_) {
if (t == tag) {
this->currentTagId_ = tagId;
emit currentTagIdChanged(currentTagId_);
return;
}
}
}
this->currentTagId_ = "";
emit currentTagIdChanged(currentTagId_);
this->currentTagId_ = "";
emit currentTagIdChanged(currentTagId_);
}
void
CommunitiesModel::toggleTagId(QString tagId)
{
if (hiddentTagIds_.contains(tagId)) {
hiddentTagIds_.removeOne(tagId);
UserSettings::instance()->setHiddenTags(hiddentTagIds_);
} else {
hiddentTagIds_.push_back(tagId);
UserSettings::instance()->setHiddenTags(hiddentTagIds_);
}
if (tagId.startsWith("tag:")) {
auto idx = tags_.indexOf(tagId.mid(4));
if (idx != -1)
emit dataChanged(index(idx + 1 + spaceOrder_.size()),
index(idx + 1 + spaceOrder_.size()),
{Hidden});
} else if (tagId.startsWith("space:")) {
auto idx = spaceOrder_.indexOf(tagId.mid(6));
if (idx != -1)
emit dataChanged(index(idx + 1), index(idx + 1), {Hidden});
}
emit hiddenTagsChanged();
if (hiddentTagIds_.contains(tagId)) {
hiddentTagIds_.removeOne(tagId);
UserSettings::instance()->setHiddenTags(hiddentTagIds_);
} else {
hiddentTagIds_.push_back(tagId);
UserSettings::instance()->setHiddenTags(hiddentTagIds_);
}
if (tagId.startsWith("tag:")) {
auto idx = tags_.indexOf(tagId.mid(4));
if (idx != -1)
emit dataChanged(
index(idx + 1 + spaceOrder_.size()), index(idx + 1 + spaceOrder_.size()), {Hidden});
} else if (tagId.startsWith("space:")) {
auto idx = spaceOrder_.indexOf(tagId.mid(6));
if (idx != -1)
emit dataChanged(index(idx + 1), index(idx + 1), {Hidden});
}
emit hiddenTagsChanged();
}

@ -15,64 +15,64 @@
class CommunitiesModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QString currentTagId READ currentTagId WRITE setCurrentTagId NOTIFY
currentTagIdChanged RESET resetCurrentTagId)
Q_PROPERTY(QStringList tags READ tags NOTIFY tagsChanged)
Q_PROPERTY(QStringList tagsWithDefault READ tagsWithDefault NOTIFY tagsChanged)
Q_OBJECT
Q_PROPERTY(QString currentTagId READ currentTagId WRITE setCurrentTagId NOTIFY
currentTagIdChanged RESET resetCurrentTagId)
Q_PROPERTY(QStringList tags READ tags NOTIFY tagsChanged)
Q_PROPERTY(QStringList tagsWithDefault READ tagsWithDefault NOTIFY tagsChanged)
public:
enum Roles
{
AvatarUrl = Qt::UserRole,
DisplayName,
Tooltip,
ChildrenHidden,
Hidden,
Id,
};
enum Roles
{
AvatarUrl = Qt::UserRole,
DisplayName,
Tooltip,
ChildrenHidden,
Hidden,
Id,
};
CommunitiesModel(QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
(void)parent;
return 1 + tags_.size() + spaceOrder_.size();
}
QVariant data(const QModelIndex &index, int role) const override;
CommunitiesModel(QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
(void)parent;
return 1 + tags_.size() + spaceOrder_.size();
}
QVariant data(const QModelIndex &index, int role) const override;
public slots:
void initializeSidebar();
void sync(const mtx::responses::Rooms &rooms);
void clear();
QString currentTagId() const { return currentTagId_; }
void setCurrentTagId(QString tagId);
void resetCurrentTagId()
{
currentTagId_.clear();
emit currentTagIdChanged(currentTagId_);
}
QStringList tags() const { return tags_; }
QStringList tagsWithDefault() const
{
QStringList tagsWD = tags_;
tagsWD.prepend("m.lowpriority");
tagsWD.prepend("m.favourite");
tagsWD.removeOne("m.server_notice");
tagsWD.removeDuplicates();
return tagsWD;
}
void toggleTagId(QString tagId);
void initializeSidebar();
void sync(const mtx::responses::Rooms &rooms);
void clear();
QString currentTagId() const { return currentTagId_; }
void setCurrentTagId(QString tagId);
void resetCurrentTagId()
{
currentTagId_.clear();
emit currentTagIdChanged(currentTagId_);
}
QStringList tags() const { return tags_; }
QStringList tagsWithDefault() const
{
QStringList tagsWD = tags_;
tagsWD.prepend("m.lowpriority");
tagsWD.prepend("m.favourite");
tagsWD.removeOne("m.server_notice");
tagsWD.removeDuplicates();
return tagsWD;
}
void toggleTagId(QString tagId);
signals:
void currentTagIdChanged(QString tagId);
void hiddenTagsChanged();
void tagsChanged();
void currentTagIdChanged(QString tagId);
void hiddenTagsChanged();
void tagsChanged();
private:
QStringList tags_;
QString currentTagId_;
QStringList hiddentTagIds_;
QStringList spaceOrder_;
std::map<QString, RoomInfo> spaces_;
QStringList tags_;
QString currentTagId_;
QStringList hiddentTagIds_;
QStringList spaceOrder_;
std::map<QString, RoomInfo> spaces_;
};

@ -13,127 +13,126 @@
QQmlComponent *
DelegateChoice::delegate() const
{
return delegate_;
return delegate_;
}
void
DelegateChoice::setDelegate(QQmlComponent *delegate)
{
if (delegate != delegate_) {
delegate_ = delegate;
emit delegateChanged();
emit changed();
}
if (delegate != delegate_) {
delegate_ = delegate;
emit delegateChanged();
emit changed();
}
}
QVariant
DelegateChoice::roleValue() const
{
return roleValue_;
return roleValue_;
}
void
DelegateChoice::setRoleValue(const QVariant &value)
{
if (value != roleValue_) {
roleValue_ = value;
emit roleValueChanged();
emit changed();
}
if (value != roleValue_) {
roleValue_ = value;
emit roleValueChanged();
emit changed();
}
}
QVariant
DelegateChooser::roleValue() const
{
return roleValue_;
return roleValue_;
}
void
DelegateChooser::setRoleValue(const QVariant &value)
{
if (value != roleValue_) {
roleValue_ = value;
recalcChild();
emit roleValueChanged();
}
if (value != roleValue_) {
roleValue_ = value;
recalcChild();
emit roleValueChanged();
}
}
QQmlListProperty<DelegateChoice>
DelegateChooser::choices()
{
return QQmlListProperty<DelegateChoice>(this,
this,
&DelegateChooser::appendChoice,
&DelegateChooser::choiceCount,
&DelegateChooser::choice,
&DelegateChooser::clearChoices);
return QQmlListProperty<DelegateChoice>(this,
this,
&DelegateChooser::appendChoice,
&DelegateChooser::choiceCount,
&DelegateChooser::choice,
&DelegateChooser::clearChoices);
}
void
DelegateChooser::appendChoice(QQmlListProperty<DelegateChoice> *p, DelegateChoice *c)
{
DelegateChooser *dc = static_cast<DelegateChooser *>(p->object);
dc->choices_.append(c);
DelegateChooser *dc = static_cast<DelegateChooser *>(p->object);
dc->choices_.append(c);
}
int
DelegateChooser::choiceCount(QQmlListProperty<DelegateChoice> *p)
{
return static_cast<DelegateChooser *>(p->object)->choices_.count();
return static_cast<DelegateChooser *>(p->object)->choices_.count();
}
DelegateChoice *
DelegateChooser::choice(QQmlListProperty<DelegateChoice> *p, int index)
{
return static_cast<DelegateChooser *>(p->object)->choices_.at(index);
return static_cast<DelegateChooser *>(p->object)->choices_.at(index);
}
void
DelegateChooser::clearChoices(QQmlListProperty<DelegateChoice> *p)
{
static_cast<DelegateChooser *>(p->object)->choices_.clear();
static_cast<DelegateChooser *>(p->object)->choices_.clear();
}
void
DelegateChooser::recalcChild()
{
for (const auto choice : qAsConst(choices_)) {
auto choiceValue = choice->roleValue();
if (!roleValue_.isValid() || !choiceValue.isValid() || choiceValue == roleValue_) {
if (child_) {
child_->setParentItem(nullptr);
child_ = nullptr;
}
choice->delegate()->create(incubator, QQmlEngine::contextForObject(this));
return;
}
for (const auto choice : qAsConst(choices_)) {
auto choiceValue = choice->roleValue();
if (!roleValue_.isValid() || !choiceValue.isValid() || choiceValue == roleValue_) {
if (child_) {
child_->setParentItem(nullptr);
child_ = nullptr;
}
choice->delegate()->create(incubator, QQmlEngine::contextForObject(this));
return;
}
}
}
void
DelegateChooser::componentComplete()
{
QQuickItem::componentComplete();
recalcChild();
QQuickItem::componentComplete();
recalcChild();
}
void
DelegateChooser::DelegateIncubator::statusChanged(QQmlIncubator::Status status)
{
if (status == QQmlIncubator::Ready) {
chooser.child_ = dynamic_cast<QQuickItem *>(object());
if (chooser.child_ == nullptr) {
nhlog::ui()->error("Delegate has to be derived of Item!");
return;
}
chooser.child_->setParentItem(&chooser);
QQmlEngine::setObjectOwnership(chooser.child_,
QQmlEngine::ObjectOwnership::JavaScriptOwnership);
emit chooser.childChanged();
} else if (status == QQmlIncubator::Error) {
for (const auto &e : errors())
nhlog::ui()->error("Error instantiating delegate: {}",
e.toString().toStdString());
if (status == QQmlIncubator::Ready) {
chooser.child_ = dynamic_cast<QQuickItem *>(object());
if (chooser.child_ == nullptr) {
nhlog::ui()->error("Delegate has to be derived of Item!");
return;
}
chooser.child_->setParentItem(&chooser);
QQmlEngine::setObjectOwnership(chooser.child_,
QQmlEngine::ObjectOwnership::JavaScriptOwnership);
emit chooser.childChanged();
} else if (status == QQmlIncubator::Error) {
for (const auto &e : errors())
nhlog::ui()->error("Error instantiating delegate: {}", e.toString().toStdString());
}
}

@ -18,73 +18,73 @@ class QQmlAdaptorModel;
class DelegateChoice : public QObject
{
Q_OBJECT
Q_CLASSINFO("DefaultProperty", "delegate")
Q_OBJECT
Q_CLASSINFO("DefaultProperty", "delegate")
public:
Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged)
Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged)
Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged)
QQmlComponent *delegate() const;
void setDelegate(QQmlComponent *delegate);
QQmlComponent *delegate() const;
void setDelegate(QQmlComponent *delegate);
QVariant roleValue() const;
void setRoleValue(const QVariant &value);
QVariant roleValue() const;
void setRoleValue(const QVariant &value);
signals:
void delegateChanged();
void roleValueChanged();
void changed();
void delegateChanged();
void roleValueChanged();
void changed();
private:
QVariant roleValue_;
QQmlComponent *delegate_ = nullptr;
QVariant roleValue_;
QQmlComponent *delegate_ = nullptr;
};
class DelegateChooser : public QQuickItem
{
Q_OBJECT
Q_CLASSINFO("DefaultProperty", "choices")
Q_OBJECT
Q_CLASSINFO("DefaultProperty", "choices")
public:
Q_PROPERTY(QQmlListProperty<DelegateChoice> choices READ choices CONSTANT)
Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged)
Q_PROPERTY(QQuickItem *child READ child NOTIFY childChanged)
Q_PROPERTY(QQmlListProperty<DelegateChoice> choices READ choices CONSTANT)
Q_PROPERTY(QVariant roleValue READ roleValue WRITE setRoleValue NOTIFY roleValueChanged)
Q_PROPERTY(QQuickItem *child READ child NOTIFY childChanged)
QQmlListProperty<DelegateChoice> choices();
QQmlListProperty<DelegateChoice> choices();
QVariant roleValue() const;
void setRoleValue(const QVariant &value);
QVariant roleValue() const;
void setRoleValue(const QVariant &value);
QQuickItem *child() const { return child_; }
QQuickItem *child() const { return child_; }
void recalcChild();
void componentComplete() override;
void recalcChild();
void componentComplete() override;
signals:
void roleChanged();
void roleValueChanged();
void childChanged();
void roleChanged();
void roleValueChanged();
void childChanged();
private:
struct DelegateIncubator : public QQmlIncubator
{
DelegateIncubator(DelegateChooser &parent)
: QQmlIncubator(QQmlIncubator::AsynchronousIfNested)
, chooser(parent)
{}
void statusChanged(QQmlIncubator::Status status) override;
DelegateChooser &chooser;
};
QVariant roleValue_;
QList<DelegateChoice *> choices_;
QQuickItem *child_ = nullptr;
DelegateIncubator incubator{*this};
static void appendChoice(QQmlListProperty<DelegateChoice> *, DelegateChoice *);
static int choiceCount(QQmlListProperty<DelegateChoice> *);
static DelegateChoice *choice(QQmlListProperty<DelegateChoice> *, int index);
static void clearChoices(QQmlListProperty<DelegateChoice> *);
struct DelegateIncubator : public QQmlIncubator
{
DelegateIncubator(DelegateChooser &parent)
: QQmlIncubator(QQmlIncubator::AsynchronousIfNested)
, chooser(parent)
{}
void statusChanged(QQmlIncubator::Status status) override;
DelegateChooser &chooser;
};
QVariant roleValue_;
QList<DelegateChoice *> choices_;
QQuickItem *child_ = nullptr;
DelegateIncubator incubator{*this};
static void appendChoice(QQmlListProperty<DelegateChoice> *, DelegateChoice *);
static int choiceCount(QQmlListProperty<DelegateChoice> *);
static DelegateChoice *choice(QQmlListProperty<DelegateChoice> *, int index);
static void clearChoices(QQmlListProperty<DelegateChoice> *);
};

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save