From 1cc8d915bc57272cc112ef965ae9991ee6745cde Mon Sep 17 00:00:00 2001 From: Nicolas Fella Date: Fri, 1 Apr 2022 10:56:19 +0000 Subject: [PATCH] Add rooms runner This allows to search for and open rooms in KRunner --- src/CMakeLists.txt | 7 +- src/main.cpp | 10 ++ src/plasma-runner-neochat.desktop | 13 +++ src/roomlistmodel.cpp | 6 ++ src/roomlistmodel.h | 2 + src/runner.cpp | 94 +++++++++++++++++ src/runner.h | 169 ++++++++++++++++++++++++++++++ 7 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 src/plasma-runner-neochat.desktop create mode 100644 src/runner.cpp create mode 100644 src/runner.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8100099a2..43f676beb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -63,7 +63,7 @@ if(NOT ANDROID) endif() if (NOT ANDROID AND NOT WIN32 AND NOT APPLE) - target_sources(neochat PRIVATE ../res_desktop.qrc) + target_sources(neochat PRIVATE ../res_desktop.qrc runner.cpp) else() target_sources(neochat PRIVATE ../res_android.qrc) endif() @@ -139,3 +139,8 @@ if (TARGET KF5::KIOWidgets) endif() install(TARGETS neochat ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) + +if (NOT ANDROID AND NOT WIN32 AND NOT APPLE) + install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins) +endif() + diff --git a/src/main.cpp b/src/main.cpp index 98ab0903d..9096b5a13 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -78,6 +78,11 @@ #include "colorschemer.h" #endif +#ifdef QT_DBUS_LIB +#include "runner.h" +#include +#endif + using namespace Quotient; class NetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory @@ -257,6 +262,11 @@ int main(int argc, char *argv[]) RoomManager::instance().setUrlArgument(parser.positionalArguments()[0]); } +#ifdef QT_DBUS_LIB + Runner runner; + QDBusConnection::sessionBus().registerObject("/RoomRunner", &runner, QDBusConnection::ExportScriptableContents); +#endif + #ifdef HAVE_KDBUSADDONS KDBusService service(KDBusService::Unique); service.connect(&service, diff --git a/src/plasma-runner-neochat.desktop b/src/plasma-runner-neochat.desktop new file mode 100644 index 000000000..1884bfe20 --- /dev/null +++ b/src/plasma-runner-neochat.desktop @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: CC0-1.0 +# SPDX-FileCopyrightText: 2022 Nicolas Fella +[Desktop Entry] +Name=NeoChat +Comment=Find rooms in NeoChat +X-KDE-ServiceTypes=Plasma/Runner +Type=Service +Icon=org.kde.neochat +X-Plasma-API=DBus +X-Plasma-DBusRunner-Service=org.kde.neochat +X-Plasma-DBusRunner-Path=/RoomRunner +X-Plasma-Request-Actions-Once=true +X-Plasma-Runner-Min-Letter-Count=3 diff --git a/src/roomlistmodel.cpp b/src/roomlistmodel.cpp index 760925ee4..cf8108dac 100644 --- a/src/roomlistmodel.cpp +++ b/src/roomlistmodel.cpp @@ -399,6 +399,12 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const if (role == SubtitleTextRole) { return room->subtitleText(); } + if (role == AvatarImageRole) { + return room->avatar(128); + } + if (role == IdRole) { + return room->id(); + } return QVariant(); } diff --git a/src/roomlistmodel.h b/src/roomlistmodel.h index 7890ebc8f..634599374 100644 --- a/src/roomlistmodel.h +++ b/src/roomlistmodel.h @@ -50,6 +50,8 @@ public: CurrentRoomRole, CategoryVisibleRole, SubtitleTextRole, + AvatarImageRole, + IdRole, }; Q_ENUM(EventRoles) diff --git a/src/runner.cpp b/src/runner.cpp new file mode 100644 index 000000000..af69f8fa2 --- /dev/null +++ b/src/runner.cpp @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2022 Nicolas Fella +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "controller.h" +#include "neochatroom.h" +#include "roomlistmodel.h" +#include "roommanager.h" +#include "runner.h" + +RemoteImage Runner::serializeImage(const QImage &image) +{ + QImage convertedImage = image.convertToFormat(QImage::Format_RGBA8888); + RemoteImage remoteImage{ + convertedImage.width(), + convertedImage.height(), + convertedImage.bytesPerLine(), + true, // hasAlpha + 8, // bitsPerSample + 4, // channels + QByteArray(reinterpret_cast(convertedImage.constBits()), convertedImage.sizeInBytes()), + }; + return remoteImage; +} + +Runner::Runner() + : QObject() +{ + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + + m_model.setSourceModel(&m_sourceModel); + + connect(&Controller::instance(), &Controller::activeConnectionChanged, this, &Runner::activeConnectionChanged); +} + +void Runner::activeConnectionChanged() +{ + m_sourceModel.setConnection(Controller::instance().activeConnection()); +} + +RemoteActions Runner::Actions() +{ + return {}; +} + +RemoteMatches Runner::Match(const QString &searchTerm) +{ + m_model.setFilterText(searchTerm); + + RemoteMatches matches; + + for (int i = 0; i < m_model.rowCount(); ++i) { + RemoteMatch match; + + const QString name = m_model.data(m_model.index(i, 0), RoomListModel::DisplayNameRole).toString(); + + match.iconName = QStringLiteral("org.kde.neochat"); + match.id = m_model.data(m_model.index(i, 0), RoomListModel::IdRole).toString(); + match.text = name; + match.relevance = 1; + const RemoteImage remoteImage = serializeImage(m_model.data(m_model.index(i, 0), RoomListModel::AvatarImageRole).value()); + match.properties.insert(QStringLiteral("icon-data"), QVariant::fromValue(remoteImage)); + match.properties.insert(QStringLiteral("subtext"), m_model.data(m_model.index(i, 0), RoomListModel::TopicRole).toString()); + + if (name.compare(searchTerm, Qt::CaseInsensitive) == 0) { + match.type = ExactMatch; + } else { + match.type = CompletionMatch; + } + + matches << match; + } + + return matches; +} + +void Runner::Run(const QString &id, const QString &actionId) +{ + Q_UNUSED(actionId); + + NeoChatRoom *room = qobject_cast(Controller::instance().activeConnection()->room(id)); + + if (!room) { + return; + } + + RoomManager::instance().enterRoom(room); + Q_EMIT Controller::instance().showWindow(); +} diff --git a/src/runner.h b/src/runner.h new file mode 100644 index 000000000..4589fc6c2 --- /dev/null +++ b/src/runner.h @@ -0,0 +1,169 @@ +// SPDX-FileCopyrightText: 2022 Nicolas Fella +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include +#include +#include +#include + +#include "roomlistmodel.h" +#include "sortfilterroomlistmodel.h" + +// Copied from KRunner/QueryMatch +enum MatchType { + NoMatch = 0, /**< Null match */ + CompletionMatch = 10, /**< Possible completion for the data of the query */ + PossibleMatch = 30, /**< Something that may match the query */ + InformationalMatch = 50, /**< A purely informational, non-runnable match, + such as the answer to a question or calculation. + The data of the match will be converted to a string + and set in the search field */ + HelperMatch = 70, /**< A match that represents an action not directly related + to activating the given search term, such as a search + in an external tool or a command learning trigger. Helper + matches tend to be generic to the query and should not + be autoactivated just because the user hits "Enter" + while typing. They must be explicitly selected to + be activated, but unlike InformationalMatch cause + an action to be triggered. */ + ExactMatch = 100, /**< An exact match to the query */ +}; + +struct RemoteMatch { + // sssuda{sv} + QString id; + QString text; + QString iconName; + MatchType type = MatchType::NoMatch; + qreal relevance = 0; + QVariantMap properties; +}; + +typedef QList RemoteMatches; + +struct RemoteAction { + QString id; + QString text; + QString iconName; +}; + +typedef QList RemoteActions; + +struct RemoteImage { + // iiibiiay (matching notification spec image-data attribute) + int width; + int height; + int rowStride; + bool hasAlpha; + int bitsPerSample; + int channels; + QByteArray data; +}; + +inline QDBusArgument &operator<<(QDBusArgument &argument, const RemoteMatch &match) +{ + argument.beginStructure(); + argument << match.id; + argument << match.text; + argument << match.iconName; + argument << match.type; + argument << match.relevance; + argument << match.properties; + argument.endStructure(); + return argument; +} + +inline const QDBusArgument &operator>>(const QDBusArgument &argument, RemoteMatch &match) +{ + argument.beginStructure(); + argument >> match.id; + argument >> match.text; + argument >> match.iconName; + uint type; + argument >> type; + match.type = static_cast(type); + argument >> match.relevance; + argument >> match.properties; + argument.endStructure(); + + return argument; +} + +inline QDBusArgument &operator<<(QDBusArgument &argument, const RemoteAction &action) +{ + argument.beginStructure(); + argument << action.id; + argument << action.text; + argument << action.iconName; + argument.endStructure(); + return argument; +} + +inline const QDBusArgument &operator>>(const QDBusArgument &argument, RemoteAction &action) +{ + argument.beginStructure(); + argument >> action.id; + argument >> action.text; + argument >> action.iconName; + argument.endStructure(); + return argument; +} + +inline QDBusArgument &operator<<(QDBusArgument &argument, const RemoteImage &image) +{ + argument.beginStructure(); + argument << image.width; + argument << image.height; + argument << image.rowStride; + argument << image.hasAlpha; + argument << image.bitsPerSample; + argument << image.channels; + argument << image.data; + argument.endStructure(); + return argument; +} + +inline const QDBusArgument &operator>>(const QDBusArgument &argument, RemoteImage &image) +{ + argument.beginStructure(); + argument >> image.width; + argument >> image.height; + argument >> image.rowStride; + argument >> image.hasAlpha; + argument >> image.bitsPerSample; + argument >> image.channels; + argument >> image.data; + argument.endStructure(); + return argument; +} + +Q_DECLARE_METATYPE(RemoteMatch) +Q_DECLARE_METATYPE(RemoteMatches) +Q_DECLARE_METATYPE(RemoteAction) +Q_DECLARE_METATYPE(RemoteActions) +Q_DECLARE_METATYPE(RemoteImage) + +class Runner : public QObject, protected QDBusContext +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.krunner1") +public: + Runner(); + + Q_SCRIPTABLE RemoteActions Actions(); + Q_SCRIPTABLE RemoteMatches Match(const QString &searchTerm); + Q_SCRIPTABLE void Run(const QString &id, const QString &actionId); + +private: + RemoteImage serializeImage(const QImage &image); + void activeConnectionChanged(); + + SortFilterRoomListModel m_model; + RoomListModel m_sourceModel; +}; +