From 686ebfdbecbcacdbdba6c6289f22479c2fe5133e Mon Sep 17 00:00:00 2001 From: Loren Burkholder <55629213+LorenDB@users.noreply.github.com> Date: Thu, 14 Apr 2022 11:02:55 -0400 Subject: [PATCH] Add D-Bus API (#916) This adds functionality for viewing joined rooms and activating rooms. --- CMakeLists.txt | 16 ++- README.md | 14 ++- src/MainWindow.cpp | 16 +++ src/MainWindow.h | 8 ++ src/MxcImageProvider.cpp | 9 ++ src/UserSettingsPage.cpp | 35 ++++++ src/UserSettingsPage.h | 12 +++ src/dbus/NhekoDBusApi.cpp | 166 +++++++++++++++++++++++++++++ src/dbus/NhekoDBusApi.h | 80 ++++++++++++++ src/dbus/NhekoDBusBackend.cpp | 87 +++++++++++++++ src/dbus/NhekoDBusBackend.h | 45 ++++++++ src/notifications/Manager.h | 5 - src/notifications/ManagerLinux.cpp | 47 +------- src/timeline/RoomlistModel.cpp | 13 +++ src/timeline/RoomlistModel.h | 9 ++ 15 files changed, 508 insertions(+), 54 deletions(-) create mode 100644 src/dbus/NhekoDBusApi.cpp create mode 100644 src/dbus/NhekoDBusApi.h create mode 100644 src/dbus/NhekoDBusBackend.cpp create mode 100644 src/dbus/NhekoDBusBackend.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f1efb47f..049e3b1e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -303,7 +303,6 @@ check_symbol_exists(backtrace_symbols_fd "execinfo.h" HAVE_BACKTRACE_SYMBOLS_FD) configure_file(cmake/nheko.h config/nheko.h) - # # 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) +# 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 # Dialogs src/dialogs/FallbackAuth.h @@ -599,7 +603,15 @@ elseif (WIN32) set(SRC_FILES ${SRC_FILES} src/notifications/ManagerWin.cpp src/wintoastlib.cpp) 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 () set(NHEKO_DEPS diff --git a/README.md b/README.md index 0a89097d..06180735 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ Specifically there is support for: - Room switcher (ctrl-K). - Light, Dark & System themes. - 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 @@ -156,6 +157,12 @@ with [Chocolatey](https://chocolatey.org/): 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 --- @@ -409,7 +416,12 @@ Also copy the respective cmark.dll to the binary dir from `build/cmark-build/src ### 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 diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 7235f93d..ffc3c6c1 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -54,6 +54,10 @@ #include "ui/UIA.h" #include "voip/WebRTCSession.h" +#ifdef NHEKO_DBUS_SYS +#include "dbus/NhekoDBusApi.h" +#endif + Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents) Q_DECLARE_METATYPE(std::vector) Q_DECLARE_METATYPE(std::vector) @@ -282,6 +286,18 @@ MainWindow::registerQmlTypes() engine()->addImageProvider(QStringLiteral("jdenticon"), new JdenticonProvider()); 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 diff --git a/src/MainWindow.h b/src/MainWindow.h index e8c6fafd..3b1ff6f5 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -54,6 +54,10 @@ public: //! Show the chat page and start communicating with the given access token. void showChatPage(); +#ifdef NHEKO_DBUS_SYS + bool dbusAvailable() const { return dbusAvailable_; } +#endif + protected: void closeEvent(QCloseEvent *event); bool event(QEvent *event) override; @@ -96,4 +100,8 @@ private: TrayIcon *trayIcon_; MxcImageProvider *imgProvider = nullptr; + +#ifdef NHEKO_DBUS_SYS + bool dbusAvailable_{false}; +#endif }; diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp index 0a91dde3..6098f0c4 100644 --- a/src/MxcImageProvider.cpp +++ b/src/MxcImageProvider.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -103,6 +104,12 @@ MxcImageProvider::download(const QString &id, bool crop, double radius) { + if (id.isEmpty()) { + nhlog::net()->warn("Attempted to download image with empty ID"); + then(id, QSize{}, QImage{}, QString{}); + return; + } + std::optional encryptionInfo; auto temp = infos.find("mxc://" + id); if (temp != infos.end()) @@ -264,6 +271,7 @@ MxcImageProvider::download(const QString &id, image.setText(QStringLiteral("original filename"), QString::fromStdString(originalFilename)); image.setText(QStringLiteral("mxc url"), "mxc://" + id); + then(id, requestedSize, image, fileInfo.absoluteFilePath()); return; } @@ -276,6 +284,7 @@ MxcImageProvider::download(const QString &id, image.setText(QStringLiteral("original filename"), QString::fromStdString(originalFilename)); image.setText(QStringLiteral("mxc url"), "mxc://" + id); + then(id, requestedSize, image, fileInfo.absoluteFilePath()); }); } catch (std::exception &e) { diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp index 636bf75f..932c3beb 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -90,6 +90,8 @@ UserSettings::load(std::optional profile) privacyScreen_ = settings.value(QStringLiteral("user/privacy_screen"), false).toBool(); privacyScreenTimeout_ = 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(); emojiFont_ = settings.value(QStringLiteral("user/emoji_font_family"), "emoji").toString(); baseFontSize_ = @@ -247,6 +249,17 @@ UserSettings::setCollapsedSpaces(QList spaces) save(); } +void +UserSettings::setExposeDBusApi(bool state) +{ + if (exposeDBusApi_ == state) + return; + + exposeDBusApi_ = state; + emit exposeDBusApiChanged(state); + save(); +} + void UserSettings::setMarkdown(bool state) { @@ -788,6 +801,7 @@ UserSettings::save() settings.setValue(QStringLiteral("use_identicon"), useIdenticon_); settings.setValue(QStringLiteral("open_image_external"), openImageExternal_); settings.setValue(QStringLiteral("open_video_external"), openVideoExternal_); + settings.setValue(QStringLiteral("expose_dbus_api"), exposeDBusApi_); settings.endGroup(); // user @@ -972,6 +986,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const return tr("User signing key"); case MasterKey: return tr("Master signing key"); + case ExposeDBusApi: + return tr("Expose room information via D-Bus"); } } else if (role == Value) { switch (index.row()) { @@ -1091,6 +1107,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const .has_value(); case MasterKey: return cache::secret(mtx::secret_storage::secrets::cross_signing_master).has_value(); + case ExposeDBusApi: + return i->exposeDBusApi(); } } else if (role == Description) { 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 " "it makes it less likely it can be stolen and it is only needed to rotate your " "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) { switch (index.row()) { @@ -1279,6 +1303,7 @@ UserSettingsModel::data(const QModelIndex &index, int role) const case OnlyShareKeysWithVerifiedUsers: case ShareKeysWithTrustedUsers: case UseOnlineKeyBackup: + case ExposeDBusApi: return Toggle; case Profile: case UserId: @@ -1711,6 +1736,13 @@ UserSettingsModel::setData(const QModelIndex &index, const QVariant &value, int } else return false; } + case ExposeDBusApi: { + if (value.userType() == QMetaType::Bool) { + i->setExposeDBusApi(value.toBool()); + return true; + } else + return false; + } } } return false; @@ -1940,4 +1972,7 @@ UserSettingsModel::UserSettingsModel(QObject *p) connect(MainWindow::instance(), &MainWindow::secretsChanged, this, [this]() { emit dataChanged(index(OnlineBackupKey), index(MasterKey), {Value, Good}); }); + connect(s.get(), &UserSettings::exposeDBusApiChanged, this, [this] { + emit dataChanged(index(ExposeDBusApi), index(ExposeDBusApi), {Value}); + }); } diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h index c34bf1fc..bcc45cdc 100644 --- a/src/UserSettingsPage.h +++ b/src/UserSettingsPage.h @@ -115,6 +115,8 @@ class UserSettings : public QObject recentReactionsChanged) Q_PROPERTY(QStringList hiddenWidgets READ hiddenWidgets WRITE setHiddenWidgets NOTIFY hiddenWidgetsChanged) + Q_PROPERTY( + bool exposeDBusApi READ exposeDBusApi WRITE setExposeDBusApi NOTIFY exposeDBusApiChanged) UserSettings(); @@ -191,6 +193,7 @@ public: void setOpenImageExternal(bool state); void setOpenVideoExternal(bool state); void setCollapsedSpaces(QList spaces); + void setExposeDBusApi(bool state); QString theme() const { return !theme_.isEmpty() ? theme_ : defaultTheme_; } bool messageHoverHighlight() const { return messageHoverHighlight_; } @@ -255,6 +258,7 @@ public: bool openImageExternal() const { return openImageExternal_; } bool openVideoExternal() const { return openVideoExternal_; } QList collapsedSpaces() const { return collapsedSpaces_; } + bool exposeDBusApi() const { return exposeDBusApi_; } signals: void groupViewStateChanged(bool state); @@ -310,6 +314,7 @@ signals: void hiddenPinsChanged(); void hiddenWidgetsChanged(); void recentReactionsChanged(); + void exposeDBusApiChanged(bool state); private: // Default to system theme if QT_QPA_PLATFORMTHEME var is set. @@ -373,6 +378,7 @@ private: bool useIdenticon_; bool openImageExternal_; bool openVideoExternal_; + bool exposeDBusApi_; QSettings settings; @@ -398,6 +404,9 @@ class UserSettingsModel : public QAbstractListModel UseIdenticon, PrivacyScreen, PrivacyScreenTimeout, +#ifdef NHEKO_DBUS_SYS + ExposeDBusApi, +#endif TimelineSection, TimelineMaxWidth, @@ -457,6 +466,9 @@ class UserSettingsModel : public QAbstractListModel AccessToken, #ifdef Q_OS_MAC ScaleFactor, +#endif +#ifndef NHEKO_DBUS_SYS + ExposeDBusApi, #endif }; diff --git a/src/dbus/NhekoDBusApi.cpp b/src/dbus/NhekoDBusApi.cpp new file mode 100644 index 00000000..edc3fa8a --- /dev/null +++ b/src/dbus/NhekoDBusApi.cpp @@ -0,0 +1,166 @@ +// SPDX-FileCopyrightText: 2010 David Sansome +// SPDX-FileCopyrightText: 2022 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "NhekoDBusApi.h" + +#include + +namespace nheko::dbus { +void +init() +{ + qDBusRegisterMetaType(); + qDBusRegisterMetaType>(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); +} + +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 + */ +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(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(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; +} diff --git a/src/dbus/NhekoDBusApi.h b/src/dbus/NhekoDBusApi.h new file mode 100644 index 00000000..47cc108a --- /dev/null +++ b/src/dbus/NhekoDBusApi.h @@ -0,0 +1,80 @@ +// SPDX-FileCopyrightText: 2022 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NHEKODBUSAPI_H +#define NHEKODBUSAPI_H + +#include +#include +#include +#include + +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 diff --git a/src/dbus/NhekoDBusBackend.cpp b/src/dbus/NhekoDBusBackend.cpp new file mode 100644 index 00000000..3645aea6 --- /dev/null +++ b/src/dbus/NhekoDBusBackend.cpp @@ -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 + +NhekoDBusBackend::NhekoDBusBackend(RoomlistModel *parent) + : QObject{parent} + , m_parent{parent} +{} + +QVector +NhekoDBusBackend::getRooms(const QDBusMessage &message) +{ + const auto roomListModel = m_parent->models; + QSharedPointer> model{ + new QVector}; + + 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(); +} diff --git a/src/dbus/NhekoDBusBackend.h b/src/dbus/NhekoDBusBackend.h new file mode 100644 index 00000000..02fd87d5 --- /dev/null +++ b/src/dbus/NhekoDBusBackend.h @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2022 Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NHEKODBUSBACKEND_H +#define NHEKODBUSBACKEND_H + +#include +#include + +#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 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 diff --git a/src/notifications/Manager.h b/src/notifications/Manager.h index 2a399a27..0a5f4caa 100644 --- a/src/notifications/Manager.h +++ b/src/notifications/Manager.h @@ -11,11 +11,6 @@ #include -// 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) #include #include diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index 76f290a8..225a6533 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -27,6 +27,7 @@ #include "MxcImageProvider.h" #include "UserSettingsPage.h" #include "Utils.h" +#include "dbus/NhekoDBusApi.h" NotificationsManager::NotificationsManager(QObject *parent) : QObject(parent) @@ -269,49 +270,3 @@ NotificationsManager::notificationClosed(uint id, uint reason) Q_UNUSED(reason); 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 - */ -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(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; -} diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp index 31e5a33d..ea4f6fa8 100644 --- a/src/timeline/RoomlistModel.cpp +++ b/src/timeline/RoomlistModel.cpp @@ -15,6 +15,10 @@ #include "TimelineViewManager.h" #include "UserSettingsPage.h" +#ifdef NHEKO_DBUS_SYS +#include +#endif + RoomlistModel::RoomlistModel(TimelineViewManager *parent) : QAbstractListModel(parent) , manager(parent) @@ -604,6 +608,15 @@ RoomlistModel::initializeRooms() nhlog::db()->info("Restored {} rooms from cache", rowCount()); 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 diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h index 73ccd929..9546d434 100644 --- a/src/timeline/RoomlistModel.h +++ b/src/timeline/RoomlistModel.h @@ -17,6 +17,10 @@ #include "TimelineModel.h" +#ifdef NHEKO_DBUS_SYS +#include "dbus/NhekoDBusBackend.h" +#endif + class TimelineViewManager; class RoomPreview @@ -138,6 +142,11 @@ private: std::map> directChatToUser; +#ifdef NHEKO_DBUS_SYS + NhekoDBusBackend *dbusInterface_; + friend class NhekoDBusBackend; +#endif + friend class FilteredRoomlistModel; };