Add D-Bus API (#916)

This adds functionality for viewing joined rooms and activating rooms.
pull/1046/head
Loren Burkholder 3 years ago committed by GitHub
parent 3329b7aab2
commit 686ebfdbec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      CMakeLists.txt
  2. 14
      README.md
  3. 16
      src/MainWindow.cpp
  4. 8
      src/MainWindow.h
  5. 9
      src/MxcImageProvider.cpp
  6. 35
      src/UserSettingsPage.cpp
  7. 12
      src/UserSettingsPage.h
  8. 166
      src/dbus/NhekoDBusApi.cpp
  9. 80
      src/dbus/NhekoDBusApi.h
  10. 87
      src/dbus/NhekoDBusBackend.cpp
  11. 45
      src/dbus/NhekoDBusBackend.h
  12. 5
      src/notifications/Manager.h
  13. 47
      src/notifications/ManagerLinux.cpp
  14. 13
      src/timeline/RoomlistModel.cpp
  15. 9
      src/timeline/RoomlistModel.h

@ -303,7 +303,6 @@ check_symbol_exists(backtrace_symbols_fd "execinfo.h" HAVE_BACKTRACE_SYMBOLS_FD)
configure_file(cmake/nheko.h config/nheko.h) configure_file(cmake/nheko.h config/nheko.h)
# #
# Declare source and header files. # Declare source and header files.
# #
@ -501,6 +500,11 @@ add_subdirectory(third_party/SingleApplication-3.3.2/)
feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
# this must be defined here to make the moc work properly
if (NOT APPLE AND NOT WIN32)
add_compile_definitions(NHEKO_DBUS_SYS)
endif()
qt5_wrap_cpp(MOC_HEADERS qt5_wrap_cpp(MOC_HEADERS
# Dialogs # Dialogs
src/dialogs/FallbackAuth.h src/dialogs/FallbackAuth.h
@ -599,7 +603,15 @@ elseif (WIN32)
set(SRC_FILES ${SRC_FILES} src/notifications/ManagerWin.cpp src/wintoastlib.cpp) set(SRC_FILES ${SRC_FILES} src/notifications/ManagerWin.cpp src/wintoastlib.cpp)
else () else ()
set(SRC_FILES ${SRC_FILES} src/notifications/ManagerLinux.cpp) set(SRC_FILES ${SRC_FILES}
src/dbus/NhekoDBusApi.cpp
src/dbus/NhekoDBusBackend.cpp
src/notifications/ManagerLinux.cpp
)
qt5_wrap_cpp(MOC_HEADERS
src/dbus/NhekoDBusApi.h
src/dbus/NhekoDBusBackend.h
)
endif () endif ()
set(NHEKO_DEPS set(NHEKO_DEPS

@ -43,6 +43,7 @@ Specifically there is support for:
- Room switcher (ctrl-K). - Room switcher (ctrl-K).
- Light, Dark & System themes. - Light, Dark & System themes.
- Creating separate profiles (command line only, use `-p name`). - Creating separate profiles (command line only, use `-p name`).
- D-Bus API to allow integration with third-party plugins (does not support Windows or macOS).
## Installation ## Installation
@ -156,6 +157,12 @@ with [Chocolatey](https://chocolatey.org/):
choco install nheko-reborn choco install nheko-reborn
``` ```
#### D-Bus plugins
nheko does not provide binaries for any D-Bus plugins. However, we do provide the following list of known plugins:
- [nheko-krunner](https://github.com/LorenDB/nheko-krunner)
### FAQ ### FAQ
--- ---
@ -409,7 +416,12 @@ Also copy the respective cmark.dll to the binary dir from `build/cmark-build/src
### Contributing ### Contributing
See [CONTRIBUTING](.github/CONTRIBUTING.md) See [CONTRIBUTING](.github/CONTRIBUTING.md).
### Using the D-Bus API
Currently, there is no documentation for the D-Bus API, so if you'd like to make use of it, come ask
for support in [#nheko:nheko.im](https://matrix.to/#/#nheko:nheko.im).
### Screens ### Screens

@ -54,6 +54,10 @@
#include "ui/UIA.h" #include "ui/UIA.h"
#include "voip/WebRTCSession.h" #include "voip/WebRTCSession.h"
#ifdef NHEKO_DBUS_SYS
#include "dbus/NhekoDBusApi.h"
#endif
Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents) Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents)
Q_DECLARE_METATYPE(std::vector<DeviceInfo>) Q_DECLARE_METATYPE(std::vector<DeviceInfo>)
Q_DECLARE_METATYPE(std::vector<mtx::responses::PublicRoomsChunk>) Q_DECLARE_METATYPE(std::vector<mtx::responses::PublicRoomsChunk>)
@ -282,6 +286,18 @@ MainWindow::registerQmlTypes()
engine()->addImageProvider(QStringLiteral("jdenticon"), new JdenticonProvider()); engine()->addImageProvider(QStringLiteral("jdenticon"), new JdenticonProvider());
QObject::connect(engine(), &QQmlEngine::quit, &QGuiApplication::quit); QObject::connect(engine(), &QQmlEngine::quit, &QGuiApplication::quit);
#ifdef NHEKO_DBUS_SYS
if (UserSettings::instance()->exposeDBusApi()) {
if (QDBusConnection::sessionBus().isConnected() &&
QDBusConnection::sessionBus().registerService(NHEKO_DBUS_SERVICE_NAME)) {
nheko::dbus::init();
nhlog::ui()->info("Initialized D-Bus");
dbusAvailable_ = true;
} else
nhlog::ui()->warn("Could not connect to D-Bus!");
}
#endif
} }
void void

@ -54,6 +54,10 @@ public:
//! Show the chat page and start communicating with the given access token. //! Show the chat page and start communicating with the given access token.
void showChatPage(); void showChatPage();
#ifdef NHEKO_DBUS_SYS
bool dbusAvailable() const { return dbusAvailable_; }
#endif
protected: protected:
void closeEvent(QCloseEvent *event); void closeEvent(QCloseEvent *event);
bool event(QEvent *event) override; bool event(QEvent *event) override;
@ -96,4 +100,8 @@ private:
TrayIcon *trayIcon_; TrayIcon *trayIcon_;
MxcImageProvider *imgProvider = nullptr; MxcImageProvider *imgProvider = nullptr;
#ifdef NHEKO_DBUS_SYS
bool dbusAvailable_{false};
#endif
}; };

@ -10,6 +10,7 @@
#include <mtxclient/crypto/client.hpp> #include <mtxclient/crypto/client.hpp>
#include <QByteArray> #include <QByteArray>
#include <QCache>
#include <QDir> #include <QDir>
#include <QFileInfo> #include <QFileInfo>
#include <QPainter> #include <QPainter>
@ -103,6 +104,12 @@ MxcImageProvider::download(const QString &id,
bool crop, bool crop,
double radius) double radius)
{ {
if (id.isEmpty()) {
nhlog::net()->warn("Attempted to download image with empty ID");
then(id, QSize{}, QImage{}, QString{});
return;
}
std::optional<mtx::crypto::EncryptedFile> encryptionInfo; std::optional<mtx::crypto::EncryptedFile> encryptionInfo;
auto temp = infos.find("mxc://" + id); auto temp = infos.find("mxc://" + id);
if (temp != infos.end()) if (temp != infos.end())
@ -264,6 +271,7 @@ MxcImageProvider::download(const QString &id,
image.setText(QStringLiteral("original filename"), image.setText(QStringLiteral("original filename"),
QString::fromStdString(originalFilename)); QString::fromStdString(originalFilename));
image.setText(QStringLiteral("mxc url"), "mxc://" + id); image.setText(QStringLiteral("mxc url"), "mxc://" + id);
then(id, requestedSize, image, fileInfo.absoluteFilePath()); then(id, requestedSize, image, fileInfo.absoluteFilePath());
return; return;
} }
@ -276,6 +284,7 @@ MxcImageProvider::download(const QString &id,
image.setText(QStringLiteral("original filename"), image.setText(QStringLiteral("original filename"),
QString::fromStdString(originalFilename)); QString::fromStdString(originalFilename));
image.setText(QStringLiteral("mxc url"), "mxc://" + id); image.setText(QStringLiteral("mxc url"), "mxc://" + id);
then(id, requestedSize, image, fileInfo.absoluteFilePath()); then(id, requestedSize, image, fileInfo.absoluteFilePath());
}); });
} catch (std::exception &e) { } catch (std::exception &e) {

@ -90,6 +90,8 @@ UserSettings::load(std::optional<QString> profile)
privacyScreen_ = settings.value(QStringLiteral("user/privacy_screen"), false).toBool(); privacyScreen_ = settings.value(QStringLiteral("user/privacy_screen"), false).toBool();
privacyScreenTimeout_ = privacyScreenTimeout_ =
settings.value(QStringLiteral("user/privacy_screen_timeout"), 0).toInt(); settings.value(QStringLiteral("user/privacy_screen_timeout"), 0).toInt();
exposeDBusApi_ = settings.value(QStringLiteral("user/expose_dbus_api"), false).toBool();
mobileMode_ = settings.value(QStringLiteral("user/mobile_mode"), false).toBool(); mobileMode_ = settings.value(QStringLiteral("user/mobile_mode"), false).toBool();
emojiFont_ = settings.value(QStringLiteral("user/emoji_font_family"), "emoji").toString(); emojiFont_ = settings.value(QStringLiteral("user/emoji_font_family"), "emoji").toString();
baseFontSize_ = baseFontSize_ =
@ -247,6 +249,17 @@ UserSettings::setCollapsedSpaces(QList<QStringList> spaces)
save(); save();
} }
void
UserSettings::setExposeDBusApi(bool state)
{
if (exposeDBusApi_ == state)
return;
exposeDBusApi_ = state;
emit exposeDBusApiChanged(state);
save();
}
void void
UserSettings::setMarkdown(bool state) UserSettings::setMarkdown(bool state)
{ {
@ -788,6 +801,7 @@ UserSettings::save()
settings.setValue(QStringLiteral("use_identicon"), useIdenticon_); settings.setValue(QStringLiteral("use_identicon"), useIdenticon_);
settings.setValue(QStringLiteral("open_image_external"), openImageExternal_); settings.setValue(QStringLiteral("open_image_external"), openImageExternal_);
settings.setValue(QStringLiteral("open_video_external"), openVideoExternal_); settings.setValue(QStringLiteral("open_video_external"), openVideoExternal_);
settings.setValue(QStringLiteral("expose_dbus_api"), exposeDBusApi_);
settings.endGroup(); // user settings.endGroup(); // user
@ -972,6 +986,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
return tr("User signing key"); return tr("User signing key");
case MasterKey: case MasterKey:
return tr("Master signing key"); return tr("Master signing key");
case ExposeDBusApi:
return tr("Expose room information via D-Bus");
} }
} else if (role == Value) { } else if (role == Value) {
switch (index.row()) { switch (index.row()) {
@ -1091,6 +1107,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
.has_value(); .has_value();
case MasterKey: case MasterKey:
return cache::secret(mtx::secret_storage::secrets::cross_signing_master).has_value(); return cache::secret(mtx::secret_storage::secrets::cross_signing_master).has_value();
case ExposeDBusApi:
return i->exposeDBusApi();
} }
} else if (role == Description) { } else if (role == Description) {
switch (index.row()) { switch (index.row()) {
@ -1235,6 +1253,12 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
"Your most important key. You don't need to have it cached, since not caching " "Your most important key. You don't need to have it cached, since not caching "
"it makes it less likely it can be stolen and it is only needed to rotate your " "it makes it less likely it can be stolen and it is only needed to rotate your "
"other signing keys."); "other signing keys.");
case ExposeDBusApi:
return tr("Allow third-party plugins and applications to load information about rooms "
"you are in via D-Bus. "
"This can have useful applications, but it also could be used for nefarious "
"purposes. Enable at your own risk.\n\n"
"This setting will take effect upon restart.");
} }
} else if (role == Type) { } else if (role == Type) {
switch (index.row()) { switch (index.row()) {
@ -1279,6 +1303,7 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
case OnlyShareKeysWithVerifiedUsers: case OnlyShareKeysWithVerifiedUsers:
case ShareKeysWithTrustedUsers: case ShareKeysWithTrustedUsers:
case UseOnlineKeyBackup: case UseOnlineKeyBackup:
case ExposeDBusApi:
return Toggle; return Toggle;
case Profile: case Profile:
case UserId: case UserId:
@ -1711,6 +1736,13 @@ UserSettingsModel::setData(const QModelIndex &index, const QVariant &value, int
} else } else
return false; return false;
} }
case ExposeDBusApi: {
if (value.userType() == QMetaType::Bool) {
i->setExposeDBusApi(value.toBool());
return true;
} else
return false;
}
} }
} }
return false; return false;
@ -1940,4 +1972,7 @@ UserSettingsModel::UserSettingsModel(QObject *p)
connect(MainWindow::instance(), &MainWindow::secretsChanged, this, [this]() { connect(MainWindow::instance(), &MainWindow::secretsChanged, this, [this]() {
emit dataChanged(index(OnlineBackupKey), index(MasterKey), {Value, Good}); emit dataChanged(index(OnlineBackupKey), index(MasterKey), {Value, Good});
}); });
connect(s.get(), &UserSettings::exposeDBusApiChanged, this, [this] {
emit dataChanged(index(ExposeDBusApi), index(ExposeDBusApi), {Value});
});
} }

@ -115,6 +115,8 @@ class UserSettings : public QObject
recentReactionsChanged) recentReactionsChanged)
Q_PROPERTY(QStringList hiddenWidgets READ hiddenWidgets WRITE setHiddenWidgets NOTIFY Q_PROPERTY(QStringList hiddenWidgets READ hiddenWidgets WRITE setHiddenWidgets NOTIFY
hiddenWidgetsChanged) hiddenWidgetsChanged)
Q_PROPERTY(
bool exposeDBusApi READ exposeDBusApi WRITE setExposeDBusApi NOTIFY exposeDBusApiChanged)
UserSettings(); UserSettings();
@ -191,6 +193,7 @@ public:
void setOpenImageExternal(bool state); void setOpenImageExternal(bool state);
void setOpenVideoExternal(bool state); void setOpenVideoExternal(bool state);
void setCollapsedSpaces(QList<QStringList> spaces); void setCollapsedSpaces(QList<QStringList> spaces);
void setExposeDBusApi(bool state);
QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; } QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; }
bool messageHoverHighlight() const { return messageHoverHighlight_; } bool messageHoverHighlight() const { return messageHoverHighlight_; }
@ -255,6 +258,7 @@ public:
bool openImageExternal() const { return openImageExternal_; } bool openImageExternal() const { return openImageExternal_; }
bool openVideoExternal() const { return openVideoExternal_; } bool openVideoExternal() const { return openVideoExternal_; }
QList<QStringList> collapsedSpaces() const { return collapsedSpaces_; } QList<QStringList> collapsedSpaces() const { return collapsedSpaces_; }
bool exposeDBusApi() const { return exposeDBusApi_; }
signals: signals:
void groupViewStateChanged(bool state); void groupViewStateChanged(bool state);
@ -310,6 +314,7 @@ signals:
void hiddenPinsChanged(); void hiddenPinsChanged();
void hiddenWidgetsChanged(); void hiddenWidgetsChanged();
void recentReactionsChanged(); void recentReactionsChanged();
void exposeDBusApiChanged(bool state);
private: private:
// Default to system theme if QT_QPA_PLATFORMTHEME var is set. // Default to system theme if QT_QPA_PLATFORMTHEME var is set.
@ -373,6 +378,7 @@ private:
bool useIdenticon_; bool useIdenticon_;
bool openImageExternal_; bool openImageExternal_;
bool openVideoExternal_; bool openVideoExternal_;
bool exposeDBusApi_;
QSettings settings; QSettings settings;
@ -398,6 +404,9 @@ class UserSettingsModel : public QAbstractListModel
UseIdenticon, UseIdenticon,
PrivacyScreen, PrivacyScreen,
PrivacyScreenTimeout, PrivacyScreenTimeout,
#ifdef NHEKO_DBUS_SYS
ExposeDBusApi,
#endif
TimelineSection, TimelineSection,
TimelineMaxWidth, TimelineMaxWidth,
@ -457,6 +466,9 @@ class UserSettingsModel : public QAbstractListModel
AccessToken, AccessToken,
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
ScaleFactor, ScaleFactor,
#endif
#ifndef NHEKO_DBUS_SYS
ExposeDBusApi,
#endif #endif
}; };

@ -0,0 +1,166 @@
// SPDX-FileCopyrightText: 2010 David Sansome <me@davidsansome.com>
// SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "NhekoDBusApi.h"
#include <QDBusMetaType>
namespace nheko::dbus {
void
init()
{
qDBusRegisterMetaType<RoomInfoItem>();
qDBusRegisterMetaType<QVector<RoomInfoItem>>();
qDBusRegisterMetaType<QImage>();
qDBusRegisterMetaType<QVersionNumber>();
}
bool
apiVersionIsCompatible(const QVersionNumber &clientAppVersion)
{
if (clientAppVersion.majorVersion() != nheko::dbus::apiVersion.majorVersion())
return false;
if (clientAppVersion.minorVersion() > nheko::dbus::apiVersion.minorVersion())
return false;
if (clientAppVersion.minorVersion() == nheko::dbus::apiVersion.minorVersion() &&
clientAppVersion.microVersion() < nheko::dbus::apiVersion.microVersion())
return false;
return true;
}
RoomInfoItem::RoomInfoItem(const QString &roomId,
const QString &alias,
const QString &title,
const QImage &image,
const int unreadNotifications,
QObject *parent)
: QObject{parent}
, roomId_{roomId}
, alias_{alias}
, roomName_{title}
, image_{image}
, unreadNotifications_{unreadNotifications}
{}
RoomInfoItem::RoomInfoItem(const RoomInfoItem &other)
: QObject{other.parent()}
, roomId_{other.roomId_}
, alias_{other.alias_}
, roomName_{other.roomName_}
, image_{other.image_}
, unreadNotifications_{other.unreadNotifications_}
{}
RoomInfoItem &
RoomInfoItem::operator=(const RoomInfoItem &other)
{
roomId_ = other.roomId_;
alias_ = other.alias_;
roomName_ = other.roomName_;
image_ = other.image_;
unreadNotifications_ = other.unreadNotifications_;
return *this;
}
QDBusArgument &
operator<<(QDBusArgument &arg, const RoomInfoItem &item)
{
arg.beginStructure();
arg << item.roomId_ << item.alias_ << item.roomName_ << item.image_
<< item.unreadNotifications_;
arg.endStructure();
return arg;
}
const QDBusArgument &
operator>>(const QDBusArgument &arg, RoomInfoItem &item)
{
arg.beginStructure();
arg >> item.roomId_ >> item.alias_ >> item.roomName_ >> item.image_ >>
item.unreadNotifications_;
if (item.image_.isNull())
item.image_ = QImage{QStringLiteral(":/icons/ui/speech-bubbles.svg")};
arg.endStructure();
return arg;
}
} // nheko::dbus
/**
* Automatic marshaling of a QImage for org.freedesktop.Notifications.Notify
*
* This function is heavily based on a function from the Clementine project (see
* http://www.clementine-player.org) and licensed under the GNU General Public
* License, version 3 or later.
*
* SPDX-FileCopyrightText: 2010 David Sansome <me@davidsansome.com>
*/
QDBusArgument &
operator<<(QDBusArgument &arg, const QImage &image)
{
if (image.isNull()) {
arg.beginStructure();
arg << 0 << 0 << 0 << false << 0 << 0 << QByteArray();
arg.endStructure();
return arg;
}
QImage i = image.height() > 100 || image.width() > 100
? image.scaledToHeight(100, Qt::SmoothTransformation)
: image;
i = std::move(i).convertToFormat(QImage::Format_RGBA8888);
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;
}
// This function, however, was merely reverse-engineered from the above function
// and is not from the Clementine project.
const QDBusArgument &
operator>>(const QDBusArgument &arg, QImage &image)
{
// garbage is used as a sort of /dev/null
int width, height, garbage;
QByteArray bits;
arg.beginStructure();
arg >> width >> height >> garbage >> garbage >> garbage >> garbage >> bits;
arg.endStructure();
image = QImage(reinterpret_cast<uchar *>(bits.data()), width, height, QImage::Format_RGBA8888);
return arg;
}
QDBusArgument &
operator<<(QDBusArgument &arg, const QVersionNumber &v)
{
arg.beginStructure();
arg << v.toString();
arg.endStructure();
return arg;
}
const QDBusArgument &
operator>>(const QDBusArgument &arg, QVersionNumber &v)
{
arg.beginStructure();
QString temp;
arg >> temp;
v = QVersionNumber::fromString(temp);
arg.endStructure();
return arg;
}

@ -0,0 +1,80 @@
// SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#ifndef NHEKODBUSAPI_H
#define NHEKODBUSAPI_H
#include <QDBusArgument>
#include <QIcon>
#include <QObject>
#include <QVersionNumber>
namespace nheko::dbus {
//! Registers all necessary classes with D-Bus. Call this before using any nheko D-Bus classes.
void
init();
//! The nheko D-Bus API version provided by this file. The API version number follows semantic
//! versioning as defined by https://semver.org.
const QVersionNumber apiVersion{0, 0, 1};
//! Compare the installed Nheko API to the version that your client app targets to see if they
//! are compatible.
bool
apiVersionIsCompatible(const QVersionNumber &clientAppVersion);
class RoomInfoItem : public QObject
{
Q_OBJECT
public:
RoomInfoItem(const QString &roomId = QString{},
const QString &alias = QString{},
const QString &title = QString{},
const QImage &image = QImage{},
const int unreadNotifications = 0,
QObject *parent = nullptr);
RoomInfoItem(const RoomInfoItem &other);
const QString &roomId() const { return roomId_; }
const QString &alias() const { return alias_; }
const QString &roomName() const { return roomName_; }
const QImage &image() const { return image_; }
int unreadNotifications() const { return unreadNotifications_; }
RoomInfoItem &operator=(const RoomInfoItem &other);
friend QDBusArgument &operator<<(QDBusArgument &arg, const nheko::dbus::RoomInfoItem &item);
friend const QDBusArgument &
operator>>(const QDBusArgument &arg, nheko::dbus::RoomInfoItem &item);
private:
QString roomId_;
QString alias_;
QString roomName_;
QImage image_;
int unreadNotifications_;
};
QDBusArgument &
operator<<(QDBusArgument &arg, const RoomInfoItem &item);
const QDBusArgument &
operator>>(const QDBusArgument &arg, RoomInfoItem &item);
} // nheko::dbus
Q_DECLARE_METATYPE(nheko::dbus::RoomInfoItem)
QDBusArgument &
operator<<(QDBusArgument &arg, const QImage &image);
const QDBusArgument &
operator>>(const QDBusArgument &arg, QImage &);
QDBusArgument &
operator<<(QDBusArgument &arg, const QVersionNumber &v);
const QDBusArgument &
operator>>(const QDBusArgument &arg, QVersionNumber &v);
#define NHEKO_DBUS_SERVICE_NAME "io.github.Nheko-Reborn.nheko"
#endif // NHEKODBUSAPI_H

@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "NhekoDBusBackend.h"
#include "Cache_p.h"
#include "ChatPage.h"
#include "Logging.h"
#include "MainWindow.h"
#include "MxcImageProvider.h"
#include "timeline/RoomlistModel.h"
#include <QDBusConnection>
NhekoDBusBackend::NhekoDBusBackend(RoomlistModel *parent)
: QObject{parent}
, m_parent{parent}
{}
QVector<nheko::dbus::RoomInfoItem>
NhekoDBusBackend::getRooms(const QDBusMessage &message)
{
const auto roomListModel = m_parent->models;
QSharedPointer<QVector<nheko::dbus::RoomInfoItem>> model{
new QVector<nheko::dbus::RoomInfoItem>};
for (const auto &room : roomListModel) {
MainWindow::instance()->imageProvider()->download(
room->roomAvatarUrl().remove("mxc://"),
{96, 96},
[message, room, model, roomListModel](
const QString &, const QSize &, const QImage &image, const QString &) {
const auto aliases = cache::client()->getRoomAliases(room->roomId().toStdString());
QString alias;
if (aliases.has_value()) {
const auto &val = aliases.value();
if (!val.alias.empty())
alias = QString::fromStdString(val.alias);
else if (val.alt_aliases.size() > 0)
alias = QString::fromStdString(val.alt_aliases.front());
}
model->push_back(nheko::dbus::RoomInfoItem{
room->roomId(), room->roomName(), alias, image, room->notificationCount()});
if (model->length() == roomListModel.size()) {
auto reply = message.createReply();
nhlog::ui()->debug("Sending {} rooms over D-Bus...", model->size());
reply << QVariant::fromValue(*model);
QDBusConnection::sessionBus().send(reply);
nhlog::ui()->debug("Rooms successfully sent to D-Bus.");
}
},
true);
}
return {};
}
void
NhekoDBusBackend::activateRoom(const QString &alias) const
{
bringWindowToTop();
m_parent->setCurrentRoom(alias);
}
void
NhekoDBusBackend::joinRoom(const QString &alias) const
{
bringWindowToTop();
ChatPage::instance()->joinRoom(alias);
}
void
NhekoDBusBackend::startDirectChat(const QString &userId) const
{
bringWindowToTop();
ChatPage::instance()->startChat(userId);
}
void
NhekoDBusBackend::bringWindowToTop() const
{
MainWindow::instance()->show();
MainWindow::instance()->raise();
}

@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#ifndef NHEKODBUSBACKEND_H
#define NHEKODBUSBACKEND_H
#include <QDBusMessage>
#include <QObject>
#include "NhekoDBusApi.h"
#include "config/nheko.h"
class RoomlistModel;
class NhekoDBusBackend : public QObject
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "im.nheko.Nheko")
public:
NhekoDBusBackend(RoomlistModel *parent);
public slots:
//! Get the nheko D-Bus API version.
Q_SCRIPTABLE QVersionNumber apiVersion() const { return nheko::dbus::apiVersion; }
//! Get the nheko version.
Q_SCRIPTABLE QString nhekoVersionString() const { return nheko::version; }
//! Call this function to get a list of all joined rooms.
Q_SCRIPTABLE QVector<nheko::dbus::RoomInfoItem> getRooms(const QDBusMessage &message);
//! Activates a currently joined room.
Q_SCRIPTABLE void activateRoom(const QString &alias) const;
//! Joins a room. It is your responsibility to ask for confirmation (if desired).
Q_SCRIPTABLE void joinRoom(const QString &alias) const;
//! Starts or activates a direct chat. It is your responsibility to ask for confirmation (if
//! desired).
Q_SCRIPTABLE void startDirectChat(const QString &userId) const;
private:
void bringWindowToTop() const;
RoomlistModel *m_parent;
};
#endif // NHEKODBUSBACKEND_H

@ -11,11 +11,6 @@
#include <mtx/responses/notifications.hpp> #include <mtx/responses/notifications.hpp>
// convenience definition
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_HAIKU)
#define NHEKO_DBUS_SYS
#endif
#if defined(NHEKO_DBUS_SYS) #if defined(NHEKO_DBUS_SYS)
#include <QtDBus/QDBusArgument> #include <QtDBus/QDBusArgument>
#include <QtDBus/QDBusInterface> #include <QtDBus/QDBusInterface>

@ -27,6 +27,7 @@
#include "MxcImageProvider.h" #include "MxcImageProvider.h"
#include "UserSettingsPage.h" #include "UserSettingsPage.h"
#include "Utils.h" #include "Utils.h"
#include "dbus/NhekoDBusApi.h"
NotificationsManager::NotificationsManager(QObject *parent) NotificationsManager::NotificationsManager(QObject *parent)
: QObject(parent) : QObject(parent)
@ -269,49 +270,3 @@ NotificationsManager::notificationClosed(uint id, uint reason)
Q_UNUSED(reason); Q_UNUSED(reason);
notificationIds.remove(id); notificationIds.remove(id);
} }
/**
* Automatic marshaling of a QImage for org.freedesktop.Notifications.Notify
*
* This function is from the Clementine project (see
* http://www.clementine-player.org) and licensed under the GNU General Public
* License, version 3 or later.
*
* SPDX-FileCopyrightText: 2010 David Sansome <me@davidsansome.com>
*/
QDBusArgument &
operator<<(QDBusArgument &arg, const QImage &image)
{
if (image.isNull()) {
arg.beginStructure();
arg << 0 << 0 << 0 << false << 0 << 0 << QByteArray();
arg.endStructure();
return arg;
}
QImage i = image.height() > 100 || image.width() > 100
? image.scaledToHeight(100, Qt::SmoothTransformation)
: image;
i = std::move(i).convertToFormat(QImage::Format_RGBA8888);
arg.beginStructure();
arg << i.width();
arg << i.height();
arg << i.bytesPerLine();
arg << i.hasAlphaChannel();
int channels = i.hasAlphaChannel() ? 4 : 3;
arg << i.depth() / channels;
arg << channels;
arg << QByteArray(reinterpret_cast<const char *>(i.bits()), i.sizeInBytes());
arg.endStructure();
return arg;
}
const QDBusArgument &
operator>>(const QDBusArgument &arg, QImage &)
{
// This is needed to link but shouldn't be called.
Q_ASSERT(0);
return arg;
}

@ -15,6 +15,10 @@
#include "TimelineViewManager.h" #include "TimelineViewManager.h"
#include "UserSettingsPage.h" #include "UserSettingsPage.h"
#ifdef NHEKO_DBUS_SYS
#include <QDBusConnection>
#endif
RoomlistModel::RoomlistModel(TimelineViewManager *parent) RoomlistModel::RoomlistModel(TimelineViewManager *parent)
: QAbstractListModel(parent) : QAbstractListModel(parent)
, manager(parent) , manager(parent)
@ -604,6 +608,15 @@ RoomlistModel::initializeRooms()
nhlog::db()->info("Restored {} rooms from cache", rowCount()); nhlog::db()->info("Restored {} rooms from cache", rowCount());
endResetModel(); endResetModel();
#ifdef NHEKO_DBUS_SYS
if (MainWindow::instance()->dbusAvailable()) {
dbusInterface_ = new NhekoDBusBackend{this};
if (!QDBusConnection::sessionBus().registerObject(
"/", dbusInterface_, QDBusConnection::ExportScriptableSlots))
nhlog::ui()->warn("Failed to register rooms with D-Bus");
}
#endif
} }
void void

@ -17,6 +17,10 @@
#include "TimelineModel.h" #include "TimelineModel.h"
#ifdef NHEKO_DBUS_SYS
#include "dbus/NhekoDBusBackend.h"
#endif
class TimelineViewManager; class TimelineViewManager;
class RoomPreview class RoomPreview
@ -138,6 +142,11 @@ private:
std::map<QString, std::vector<QString>> directChatToUser; std::map<QString, std::vector<QString>> directChatToUser;
#ifdef NHEKO_DBUS_SYS
NhekoDBusBackend *dbusInterface_;
friend class NhekoDBusBackend;
#endif
friend class FilteredRoomlistModel; friend class FilteredRoomlistModel;
}; };

Loading…
Cancel
Save