mirror of https://github.com/Nheko-Reborn/nheko
Add D-Bus API (#916)
This adds functionality for viewing joined rooms and activating rooms.pull/1046/head
parent
3329b7aab2
commit
686ebfdbec
@ -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
|
Loading…
Reference in new issue