diff --git a/src/Cache.cpp b/src/Cache.cpp
index 009cbabc..d9d1134e 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -952,6 +952,8 @@ Cache::saveState(const mtx::responses::Sync &res)
saveInvites(txn, res.rooms.invite);
+ savePresence(txn, res.presence);
+
removeLeftRooms(txn, res.rooms.leave);
txn.commit();
@@ -1037,6 +1039,21 @@ Cache::saveInvite(lmdb::txn &txn,
}
}
+void
+Cache::savePresence(
+ lmdb::txn &txn,
+ const std::vector<mtx::events::Event<mtx::events::presence::Presence>> &presenceUpdates)
+{
+ for (const auto &update : presenceUpdates) {
+ auto presenceDb = getPresenceDb(txn);
+
+ lmdb::dbi_put(txn,
+ presenceDb,
+ lmdb::val(update.sender),
+ lmdb::val(json(update.content).dump()));
+ }
+}
+
std::vector<std::string>
Cache::roomsWithStateUpdates(const mtx::responses::Sync &res)
{
@@ -2254,6 +2271,50 @@ Cache::removeAvatarUrl(const QString &room_id, const QString &user_id)
AvatarUrls.remove(fmt);
}
+mtx::presence::PresenceState
+Cache::presenceState(const std::string &user_id)
+{
+ lmdb::val presenceVal;
+
+ auto txn = lmdb::txn::begin(env_);
+ auto db = getPresenceDb(txn);
+ auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), presenceVal);
+
+ mtx::presence::PresenceState state = mtx::presence::offline;
+
+ if (res) {
+ mtx::events::presence::Presence presence =
+ json::parse(std::string(presenceVal.data(), presenceVal.size()));
+ state = presence.presence;
+ }
+
+ txn.commit();
+
+ return state;
+}
+
+std::string
+Cache::statusMessage(const std::string &user_id)
+{
+ lmdb::val presenceVal;
+
+ auto txn = lmdb::txn::begin(env_);
+ auto db = getPresenceDb(txn);
+ auto res = lmdb::dbi_get(txn, db, lmdb::val(user_id), presenceVal);
+
+ std::string status_msg;
+
+ if (res) {
+ mtx::events::presence::Presence presence =
+ json::parse(std::string(presenceVal.data(), presenceVal.size()));
+ status_msg = presence.status_msg;
+ }
+
+ txn.commit();
+
+ return status_msg;
+}
+
void
to_json(json &j, const RoomInfo &info)
{
@@ -2425,6 +2486,17 @@ insertAvatarUrl(const QString &room_id, const QString &user_id, const QString &a
instance_->insertAvatarUrl(room_id, user_id, avatar_url);
}
+mtx::presence::PresenceState
+presenceState(const std::string &user_id)
+{
+ return instance_->presenceState(user_id);
+}
+std::string
+statusMessage(const std::string &user_id)
+{
+ return instance_->statusMessage(user_id);
+}
+
//! Load saved data for the display names & avatars.
void
populateMembers()
diff --git a/src/Cache.h b/src/Cache.h
index 12465c9d..b5275623 100644
--- a/src/Cache.h
+++ b/src/Cache.h
@@ -54,6 +54,12 @@ insertDisplayName(const QString &room_id, const QString &user_id, const QString
void
insertAvatarUrl(const QString &room_id, const QString &user_id, const QString &avatar_url);
+// presence
+mtx::presence::PresenceState
+presenceState(const std::string &user_id);
+std::string
+statusMessage(const std::string &user_id);
+
//! Load saved data for the display names & avatars.
void
populateMembers();
diff --git a/src/Cache_p.h b/src/Cache_p.h
index 0d66a608..892b66a5 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -52,6 +52,10 @@ public:
static QString displayName(const QString &room_id, const QString &user_id);
static QString avatarUrl(const QString &room_id, const QString &user_id);
+ // presence
+ mtx::presence::PresenceState presenceState(const std::string &user_id);
+ std::string statusMessage(const std::string &user_id);
+
static void removeDisplayName(const QString &room_id, const QString &user_id);
static void removeAvatarUrl(const QString &room_id, const QString &user_id);
@@ -377,6 +381,10 @@ private:
void saveInvites(lmdb::txn &txn,
const std::map<std::string, mtx::responses::InvitedRoom> &rooms);
+ void savePresence(
+ lmdb::txn &txn,
+ const std::vector<mtx::events::Event<mtx::events::presence::Presence>> &presenceUpdates);
+
//! Sends signals for the rooms that are removed.
void removeLeftRooms(lmdb::txn &txn,
const std::map<std::string, mtx::responses::LeftRoom> &rooms)
@@ -430,6 +438,11 @@ private:
return lmdb::dbi::open(txn, std::string(room_id + "/mentions").c_str(), MDB_CREATE);
}
+ lmdb::dbi getPresenceDb(lmdb::txn &txn)
+ {
+ return lmdb::dbi::open(txn, "presence", MDB_CREATE);
+ }
+
//! Retrieves or creates the database that stores the open OLM sessions between our device
//! and the given curve25519 key which represents another device.
//!
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 0ca20c52..3b8af33a 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -61,6 +61,7 @@ constexpr size_t MAX_ONETIME_KEYS = 50;
Q_DECLARE_METATYPE(std::optional<mtx::crypto::EncryptedFile>)
Q_DECLARE_METATYPE(std::optional<RelatedInfo>)
+Q_DECLARE_METATYPE(mtx::presence::PresenceState)
ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
: QWidget(parent)
@@ -72,6 +73,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
qRegisterMetaType<std::optional<mtx::crypto::EncryptedFile>>();
qRegisterMetaType<std::optional<RelatedInfo>>();
+ qRegisterMetaType<mtx::presence::PresenceState>();
topLayout_ = new QHBoxLayout(this);
topLayout_->setSpacing(0);
@@ -990,7 +992,9 @@ ChatPage::startInitialSync()
nhlog::net()->info("trying initial sync");
mtx::http::SyncOpts opts;
- opts.timeout = 0;
+ opts.timeout = 0;
+ opts.set_presence = currentPresence();
+
http::client()->sync(
opts,
std::bind(
@@ -1001,6 +1005,7 @@ void
ChatPage::trySync()
{
mtx::http::SyncOpts opts;
+ opts.set_presence = currentPresence();
if (!connectivityTimer_.isActive())
connectivityTimer_.start();
@@ -1228,6 +1233,39 @@ ChatPage::sendTypingNotifications()
});
}
+QString
+ChatPage::status() const
+{
+ return QString::fromStdString(cache::statusMessage(utils::localUser().toStdString()));
+}
+
+void
+ChatPage::setStatus(const QString &status)
+{
+ http::client()->put_presence_status(
+ currentPresence(), status.toStdString(), [](mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn("failed to set presence status_msg: {}",
+ err->matrix_error.error);
+ }
+ });
+}
+
+mtx::presence::PresenceState
+ChatPage::currentPresence() const
+{
+ switch (userSettings_->presence()) {
+ case UserSettings::Presence::Online:
+ return mtx::presence::online;
+ case UserSettings::Presence::Unavailable:
+ return mtx::presence::unavailable;
+ case UserSettings::Presence::Offline:
+ return mtx::presence::offline;
+ default:
+ return mtx::presence::online;
+ }
+}
+
void
ChatPage::initialSyncHandler(const mtx::responses::Sync &res, mtx::http::RequestErr err)
{
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 8c33a63e..c38d7717 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -89,6 +89,11 @@ public:
void initiateLogout();
void focusMessageInput();
+ QString status() const;
+ void setStatus(const QString &status);
+
+ mtx::presence::PresenceState currentPresence() const;
+
public slots:
void leaveRoom(const QString &room_id);
void createRoom(const mtx::requests::CreateRoom &req);
@@ -154,6 +159,7 @@ signals:
const QImage &icon);
void updateGroupsInfo(const mtx::responses::JoinedGroups &groups);
+ void retrievedPresence(const QString &statusMsg, mtx::presence::PresenceState state);
void themeChanged();
void decryptSidebarChanged();
diff --git a/src/UserInfoWidget.cpp b/src/UserInfoWidget.cpp
index e11aa6aa..f8e94431 100644
--- a/src/UserInfoWidget.cpp
+++ b/src/UserInfoWidget.cpp
@@ -16,7 +16,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <QInputDialog>
#include <QLabel>
+#include <QMenu>
#include <QPainter>
#include <QStyle>
#include <QStyleOption>
@@ -24,10 +26,12 @@
#include <iostream>
+#include "ChatPage.h"
#include "Config.h"
#include "MainWindow.h"
#include "Splitter.h"
#include "UserInfoWidget.h"
+#include "UserSettingsPage.h"
#include "ui/Avatar.h"
#include "ui/FlatButton.h"
#include "ui/OverlayModal.h"
@@ -105,6 +109,52 @@ UserInfoWidget::UserInfoWidget(QWidget *parent)
connect(logoutButton_, &QPushButton::clicked, this, []() {
MainWindow::instance()->openLogoutDialog();
});
+
+ menu = new QMenu(this);
+
+ auto setStatusAction = menu->addAction(tr("Set custom status message"));
+ connect(setStatusAction, &QAction::triggered, this, [this]() {
+ bool ok = false;
+ QString text = QInputDialog::getText(this,
+ tr("Custom status message"),
+ tr("Status:"),
+ QLineEdit::Normal,
+ ChatPage::instance()->status(),
+ &ok);
+ if (ok)
+ ChatPage::instance()->setStatus(text);
+ });
+
+#if 0 // disable presence menu until issues in synapse are resolved
+ auto setAutoPresence = menu->addAction(tr("Set presence automatically"));
+ connect(setAutoPresence, &QAction::triggered, this, []() {
+ ChatPage::instance()->userSettings()->setPresence(
+ UserSettings::Presence::AutomaticPresence);
+ ChatPage::instance()->setStatus(ChatPage::instance()->status());
+ });
+ auto setOnline = menu->addAction(tr("Online"));
+ connect(setOnline, &QAction::triggered, this, []() {
+ ChatPage::instance()->userSettings()->setPresence(UserSettings::Presence::Online);
+ ChatPage::instance()->setStatus(ChatPage::instance()->status());
+ });
+ auto setUnavailable = menu->addAction(tr("Unavailable"));
+ connect(setUnavailable, &QAction::triggered, this, []() {
+ ChatPage::instance()->userSettings()->setPresence(
+ UserSettings::Presence::Unavailable);
+ ChatPage::instance()->setStatus(ChatPage::instance()->status());
+ });
+ auto setOffline = menu->addAction(tr("Offline"));
+ connect(setOffline, &QAction::triggered, this, []() {
+ ChatPage::instance()->userSettings()->setPresence(UserSettings::Presence::Offline);
+ ChatPage::instance()->setStatus(ChatPage::instance()->status());
+ });
+#endif
+}
+
+void
+UserInfoWidget::contextMenuEvent(QContextMenuEvent *event)
+{
+ menu->popup(event->globalPos());
}
void
diff --git a/src/UserInfoWidget.h b/src/UserInfoWidget.h
index 575ade52..03ab2cf0 100644
--- a/src/UserInfoWidget.h
+++ b/src/UserInfoWidget.h
@@ -26,6 +26,7 @@ class OverlayModal;
class QLabel;
class QHBoxLayout;
class QVBoxLayout;
+class QMenu;
class UserInfoWidget : public QWidget
{
@@ -48,6 +49,7 @@ public:
protected:
void resizeEvent(QResizeEvent *event) override;
void paintEvent(QPaintEvent *event) override;
+ void contextMenuEvent(QContextMenuEvent *) override;
private:
Avatar *userAvatar_;
@@ -70,4 +72,6 @@ private:
int logoutButtonSize_;
QColor borderColor_;
+
+ QMenu *menu = nullptr;
};
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 4e9e0cb0..88cbd1c9 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -34,6 +34,7 @@
#include <QStandardPaths>
#include <QString>
#include <QTextStream>
+#include <QtQml>
#include "Cache.h"
#include "Config.h"
@@ -73,6 +74,9 @@ UserSettings::load()
decryptSidebar_ = settings.value("user/decrypt_sidebar", true).toBool();
emojiFont_ = settings.value("user/emoji_font_family", "default").toString();
baseFontSize_ = settings.value("user/font_size", QFont().pointSizeF()).toDouble();
+ presence_ =
+ settings.value("user/presence", QVariant::fromValue(Presence::AutomaticPresence))
+ .value<Presence>();
applyTheme();
}
@@ -255,6 +259,16 @@ UserSettings::setEmojiFontFamily(QString family)
}
void
+UserSettings::setPresence(Presence state)
+{
+ if (state == presence_)
+ return;
+ presence_ = state;
+ emit presenceChanged(state);
+ save();
+}
+
+void
UserSettings::setTheme(QString theme)
{
if (theme == theme)
@@ -349,6 +363,7 @@ UserSettings::save()
settings.setValue("theme", theme());
settings.setValue("font_family", font_);
settings.setValue("emoji_font_family", emojiFont_);
+ settings.setValue("presence", QVariant::fromValue(presence_));
settings.endGroup();
diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index c90dc759..d2a1c641 100644
--- a/src/UserSettingsPage.h
+++ b/src/UserSettingsPage.h
@@ -70,10 +70,20 @@ class UserSettings : public QObject
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)
public:
UserSettings();
+ enum class Presence
+ {
+ AutomaticPresence,
+ Online,
+ Unavailable,
+ Offline,
+ };
+ Q_ENUM(Presence);
+
void save();
void load();
void applyTheme();
@@ -96,6 +106,7 @@ public:
void setAlertOnNotification(bool state);
void setAvatarCircles(bool state);
void setDecryptSidebar(bool state);
+ void setPresence(Presence state);
QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; }
bool messageHoverHighlight() const { return messageHoverHighlight_; }
@@ -120,6 +131,7 @@ public:
double fontSize() const { return baseFontSize_; }
QString font() const { return font_; }
QString emojiFont() const { return emojiFont_; }
+ Presence presence() const { return presence_; }
signals:
void groupViewStateChanged(bool state);
@@ -141,6 +153,7 @@ signals:
void fontSizeChanged(double state);
void fontChanged(QString state);
void emojiFontChanged(QString state);
+ void presenceChanged(Presence state);
private:
// Default to system theme if QT_QPA_PLATFORMTHEME var is set.
@@ -167,6 +180,7 @@ private:
double baseFontSize_;
QString font_;
QString emojiFont_;
+ Presence presence_;
};
class HorizontalLine : public QFrame
diff --git a/src/main.cpp b/src/main.cpp
index ec4f638d..46691e6f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -104,6 +104,18 @@ createCacheDirectory()
int
main(int argc, char *argv[])
{
+ // needed for settings so need to register before any settings are read to prevent warings
+ qRegisterMetaType<UserSettings::Presence>();
+
+ 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();
@@ -113,12 +125,6 @@ main(int argc, char *argv[])
}
#endif
- 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);
SingleApplication app(argc,
argv,
false,
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index d6f9fde1..151cfb4e 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -59,6 +59,18 @@ TimelineViewManager::userColor(QString id, QColor background)
return userColors.value(id);
}
+QString
+TimelineViewManager::userPresence(QString id) const
+{
+ return QString::fromStdString(
+ mtx::presence::to_string(cache::presenceState(id.toStdString())));
+}
+QString
+TimelineViewManager::userStatus(QString id) const
+{
+ return QString::fromStdString(cache::statusMessage(id.toStdString()));
+}
+
TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettings, QWidget *parent)
: imgProvider(new MxcImageProvider())
, colorImgProvider(new ColorImageProvider())
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 48505bc0..ed095058 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -44,6 +44,9 @@ public:
Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId) const;
Q_INVOKABLE QColor userColor(QString id, QColor background);
+ Q_INVOKABLE QString userPresence(QString id) const;
+ Q_INVOKABLE QString userStatus(QString id) const;
+
signals:
void clearRoomMessageCount(QString roomid);
void updateRoomsLastMessage(QString roomid, const DescInfo &info);
|