forked from quickshell/quickshell
io/ipchandler: add IpcHandler and qs msg
Also reworks the whole ipc system to use serialized variants.
This commit is contained in:
parent
3690812919
commit
5e2fb14551
14 changed files with 1428 additions and 27 deletions
|
@ -67,6 +67,10 @@ void EngineGeneration::destroy() {
|
|||
this->watcher = nullptr;
|
||||
}
|
||||
|
||||
for (auto* extension: this->extensions.values()) {
|
||||
delete extension;
|
||||
}
|
||||
|
||||
if (this->root != nullptr) {
|
||||
QObject::connect(this->root, &QObject::destroyed, this, [this]() {
|
||||
// prevent further js execution between garbage collection and engine destruction.
|
||||
|
@ -285,6 +289,18 @@ void EngineGeneration::incubationControllerDestroyed() {
|
|||
}
|
||||
}
|
||||
|
||||
void EngineGeneration::registerExtension(const void* key, EngineGenerationExt* extension) {
|
||||
if (this->extensions.contains(key)) {
|
||||
delete this->extensions.value(key);
|
||||
}
|
||||
|
||||
this->extensions.insert(key, extension);
|
||||
}
|
||||
|
||||
EngineGenerationExt* EngineGeneration::findExtension(const void* key) {
|
||||
return this->extensions.value(key);
|
||||
}
|
||||
|
||||
void EngineGeneration::quit() {
|
||||
this->shouldTerminate = true;
|
||||
this->destroy();
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <qcontainerfwd.h>
|
||||
#include <qdir.h>
|
||||
#include <qfilesystemwatcher.h>
|
||||
#include <qhash.h>
|
||||
#include <qicon.h>
|
||||
#include <qobject.h>
|
||||
#include <qpair.h>
|
||||
|
@ -20,6 +21,13 @@
|
|||
class RootWrapper;
|
||||
class QuickshellGlobal;
|
||||
|
||||
class EngineGenerationExt {
|
||||
public:
|
||||
EngineGenerationExt() = default;
|
||||
virtual ~EngineGenerationExt() = default;
|
||||
Q_DISABLE_COPY_MOVE(EngineGenerationExt);
|
||||
};
|
||||
|
||||
class EngineGeneration: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
|
@ -35,6 +43,10 @@ public:
|
|||
void registerIncubationController(QQmlIncubationController* controller);
|
||||
void deregisterIncubationController(QQmlIncubationController* controller);
|
||||
|
||||
// takes ownership
|
||||
void registerExtension(const void* key, EngineGenerationExt* extension);
|
||||
EngineGenerationExt* findExtension(const void* key);
|
||||
|
||||
static EngineGeneration* findEngineGeneration(QQmlEngine* engine);
|
||||
static EngineGeneration* findObjectGeneration(QObject* object);
|
||||
|
||||
|
@ -78,6 +90,7 @@ private:
|
|||
void postReload();
|
||||
void assignIncubationController();
|
||||
QVector<QPair<QQmlIncubationController*, QObject*>> incubationControllers;
|
||||
QHash<const void*, EngineGenerationExt*> extensions;
|
||||
|
||||
bool destroying = false;
|
||||
bool shouldTerminate = false;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#include "ipc.hpp"
|
||||
#include <functional>
|
||||
#include <variant>
|
||||
|
||||
#include <qbuffer.h>
|
||||
#include <qlocalserver.h>
|
||||
#include <qlocalsocket.h>
|
||||
#include <qlogging.h>
|
||||
|
@ -8,6 +10,7 @@
|
|||
#include <qobject.h>
|
||||
|
||||
#include "generation.hpp"
|
||||
#include "ipccommand.hpp"
|
||||
#include "paths.hpp"
|
||||
|
||||
namespace qs::ipc {
|
||||
|
@ -62,20 +65,21 @@ void IpcServerConnection::onReadyRead() {
|
|||
this->stream.startTransaction();
|
||||
|
||||
this->stream.startTransaction();
|
||||
auto command = IpcCommand::Unknown;
|
||||
IpcCommand command;
|
||||
this->stream >> command;
|
||||
if (!this->stream.commitTransaction()) return;
|
||||
|
||||
switch (command) {
|
||||
case IpcCommand::Kill:
|
||||
qInfo() << "Exiting due to IPC request.";
|
||||
EngineGeneration::currentGeneration()->quit();
|
||||
break;
|
||||
default:
|
||||
qCCritical(logIpc) << "Received invalid IPC command from" << this;
|
||||
this->socket->disconnectFromServer();
|
||||
break;
|
||||
}
|
||||
std::visit(
|
||||
[this]<typename Command>(Command& command) {
|
||||
if constexpr (std::is_same_v<std::monostate, Command>) {
|
||||
qCCritical(logIpc) << "Received invalid IPC command from" << this;
|
||||
this->socket->disconnectFromServer();
|
||||
} else {
|
||||
command.exec(this);
|
||||
}
|
||||
},
|
||||
command
|
||||
);
|
||||
|
||||
if (!this->stream.commitTransaction()) return;
|
||||
}
|
||||
|
@ -94,11 +98,7 @@ bool IpcClient::isConnected() const { return this->socket.isValid(); }
|
|||
void IpcClient::waitForConnected() { this->socket.waitForConnected(); }
|
||||
void IpcClient::waitForDisconnected() { this->socket.waitForDisconnected(); }
|
||||
|
||||
void IpcClient::kill() {
|
||||
qCDebug(logIpc) << "Sending kill command...";
|
||||
this->stream << IpcCommand::Kill;
|
||||
this->socket.flush();
|
||||
}
|
||||
void IpcClient::kill() { this->sendMessage(IpcCommand(IpcKillCommand())); }
|
||||
|
||||
void IpcClient::onError(QLocalSocket::LocalSocketError error) {
|
||||
qCCritical(logIpc) << "Socket Error" << error;
|
||||
|
@ -116,4 +116,10 @@ int IpcClient::connect(const QString& id, const std::function<void(IpcClient& cl
|
|||
callback(client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void IpcKillCommand::exec(IpcServerConnection* /*unused*/) {
|
||||
qInfo() << "Exiting due to IPC request.";
|
||||
EngineGeneration::currentGeneration()->quit();
|
||||
}
|
||||
|
||||
} // namespace qs::ipc
|
||||
|
|
168
src/core/ipc.hpp
168
src/core/ipc.hpp
|
@ -1,17 +1,133 @@
|
|||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include <qflags.h>
|
||||
#include <qlocalserver.h>
|
||||
#include <qlocalsocket.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
template <typename... Types>
|
||||
constexpr void assertSerializable() {
|
||||
// monostate being zero ensures transactional reads wont break
|
||||
static_assert(
|
||||
std::is_same_v<std::variant_alternative_t<0, std::variant<Types...>>, std::monostate>,
|
||||
"Serialization of variants without std::monostate at index 0 is disallowed."
|
||||
);
|
||||
|
||||
static_assert(
|
||||
sizeof...(Types) <= std::numeric_limits<quint8>::max(),
|
||||
"Serialization of variants that can't fit the tag in a uint8 is disallowed."
|
||||
);
|
||||
}
|
||||
|
||||
template <typename... Types>
|
||||
QDataStream& operator<<(QDataStream& stream, const std::variant<Types...>& variant) {
|
||||
assertSerializable<Types...>();
|
||||
|
||||
if (variant.valueless_by_exception()) {
|
||||
stream << static_cast<quint8>(0); // must be monostate
|
||||
} else {
|
||||
stream << static_cast<quint8>(variant.index());
|
||||
std::visit([&]<typename T>(const T& value) { stream << value; }, variant);
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
template <typename... Types>
|
||||
constexpr bool forEachTypeIndex(const auto& f) {
|
||||
return [&]<size_t... Index>(std::index_sequence<Index...>) {
|
||||
return (f(std::in_place_index_t<Index>()) || ...);
|
||||
}(std::index_sequence_for<Types...>());
|
||||
}
|
||||
|
||||
template <typename... Types>
|
||||
std::variant<Types...> createIndexedOrMonostate(size_t index, std::variant<Types...>& variant) {
|
||||
assertSerializable<Types...>();
|
||||
|
||||
const auto initialized =
|
||||
forEachTypeIndex<Types...>([index, &variant]<size_t Index>(std::in_place_index_t<Index>) {
|
||||
if (index == Index) {
|
||||
variant.template emplace<Index>();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (!initialized) {
|
||||
variant = std::monostate();
|
||||
}
|
||||
|
||||
return variant;
|
||||
}
|
||||
|
||||
template <typename... Types>
|
||||
QDataStream& operator>>(QDataStream& stream, std::variant<Types...>& variant) {
|
||||
assertSerializable<Types...>();
|
||||
|
||||
quint8 index = 0;
|
||||
stream >> index;
|
||||
|
||||
createIndexedOrMonostate<Types...>(index, variant);
|
||||
std::visit([&]<typename T>(T& value) { stream >> value; }, variant);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
template <typename... Types>
|
||||
QDataStream& streamInValues(QDataStream& stream, const Types&... types) {
|
||||
return (stream << ... << types);
|
||||
}
|
||||
|
||||
template <typename... Types>
|
||||
QDataStream& streamOutValues(QDataStream& stream, Types&... types) {
|
||||
return (stream >> ... >> types);
|
||||
}
|
||||
|
||||
// NOLINTBEGIN
|
||||
#define DEFINE_SIMPLE_DATASTREAM_OPS(Type, ...) \
|
||||
inline QDataStream& operator<<(QDataStream& stream, const Type& __VA_OPT__(data)) { \
|
||||
return streamInValues(stream __VA_OPT__(, __VA_ARGS__)); \
|
||||
} \
|
||||
\
|
||||
inline QDataStream& operator>>(QDataStream& stream, Type& __VA_OPT__(data)) { \
|
||||
return streamOutValues(stream __VA_OPT__(, __VA_ARGS__)); \
|
||||
}
|
||||
// NOLINTEND
|
||||
|
||||
DEFINE_SIMPLE_DATASTREAM_OPS(std::monostate);
|
||||
|
||||
namespace qs::ipc {
|
||||
|
||||
enum class IpcCommand : quint8 {
|
||||
Unknown = 0,
|
||||
Kill,
|
||||
Q_DECLARE_LOGGING_CATEGORY(logIpc);
|
||||
|
||||
template <typename T>
|
||||
class MessageStream {
|
||||
public:
|
||||
explicit MessageStream(QDataStream* stream, QLocalSocket* socket)
|
||||
: stream(stream)
|
||||
, socket(socket) {}
|
||||
|
||||
template <typename V>
|
||||
MessageStream& operator<<(V value) {
|
||||
*this->stream << T(value);
|
||||
this->socket->flush();
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
QDataStream* stream;
|
||||
QLocalSocket* socket;
|
||||
};
|
||||
|
||||
class IpcServer: public QObject {
|
||||
|
@ -35,13 +151,24 @@ class IpcServerConnection: public QObject {
|
|||
public:
|
||||
explicit IpcServerConnection(QLocalSocket* socket, IpcServer* server);
|
||||
|
||||
template <typename T>
|
||||
void respond(const T& message) {
|
||||
this->stream << message;
|
||||
this->socket->flush();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
MessageStream<T> responseStream() {
|
||||
return MessageStream<T>(&this->stream, this->socket);
|
||||
}
|
||||
|
||||
// public for access by nonlocal handlers
|
||||
QLocalSocket* socket;
|
||||
QDataStream stream;
|
||||
|
||||
private slots:
|
||||
void onDisconnected();
|
||||
void onReadyRead();
|
||||
|
||||
private:
|
||||
QLocalSocket* socket;
|
||||
QDataStream stream;
|
||||
};
|
||||
|
||||
class IpcClient: public QObject {
|
||||
|
@ -56,19 +183,38 @@ public:
|
|||
|
||||
void kill();
|
||||
|
||||
template <typename T>
|
||||
void sendMessage(const T& message) {
|
||||
this->stream << message;
|
||||
this->socket.flush();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool waitForResponse(T& slot) {
|
||||
while (this->socket.waitForReadyRead(-1)) {
|
||||
this->stream.startTransaction();
|
||||
this->stream >> slot;
|
||||
if (!this->stream.commitTransaction()) continue;
|
||||
return true;
|
||||
}
|
||||
|
||||
qCCritical(logIpc) << "Error occurred while waiting for response.";
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] static int
|
||||
connect(const QString& id, const std::function<void(IpcClient& client)>& callback);
|
||||
|
||||
// public for access by nonlocal handlers
|
||||
QLocalSocket socket;
|
||||
QDataStream stream;
|
||||
|
||||
signals:
|
||||
void connected();
|
||||
void disconnected();
|
||||
|
||||
private slots:
|
||||
static void onError(QLocalSocket::LocalSocketError error);
|
||||
|
||||
private:
|
||||
QLocalSocket socket;
|
||||
QDataStream stream;
|
||||
};
|
||||
|
||||
} // namespace qs::ipc
|
||||
|
|
20
src/core/ipccommand.hpp
Normal file
20
src/core/ipccommand.hpp
Normal file
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include <variant>
|
||||
|
||||
#include "../io/ipccomm.hpp"
|
||||
#include "ipc.hpp"
|
||||
|
||||
namespace qs::ipc {
|
||||
|
||||
struct IpcKillCommand: std::monostate {
|
||||
static void exec(IpcServerConnection* /*unused*/);
|
||||
};
|
||||
|
||||
using IpcCommand = std::variant<
|
||||
std::monostate,
|
||||
IpcKillCommand,
|
||||
qs::io::ipc::comm::QueryMetadataCommand,
|
||||
qs::io::ipc::comm::StringCallCommand>;
|
||||
|
||||
} // namespace qs::ipc
|
|
@ -6,6 +6,7 @@
|
|||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <CLI/App.hpp>
|
||||
#include <CLI/CLI.hpp> // NOLINT: Need to include this for impls of some CLI11 classes
|
||||
|
@ -37,6 +38,7 @@
|
|||
#include <qtextstream.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../io/ipccomm.hpp"
|
||||
#include "build.hpp"
|
||||
#include "common.hpp"
|
||||
#include "instanceinfo.hpp"
|
||||
|
@ -157,10 +159,18 @@ struct CommandState {
|
|||
bool json = false;
|
||||
} output;
|
||||
|
||||
struct {
|
||||
bool info = false;
|
||||
QStringOption target;
|
||||
QStringOption function;
|
||||
std::vector<QStringOption> arguments;
|
||||
} ipc;
|
||||
|
||||
struct {
|
||||
CLI::App* log = nullptr;
|
||||
CLI::App* list = nullptr;
|
||||
CLI::App* kill = nullptr;
|
||||
CLI::App* msg = nullptr;
|
||||
} subcommand;
|
||||
|
||||
struct {
|
||||
|
@ -174,6 +184,7 @@ struct CommandState {
|
|||
int readLogFile(CommandState& cmd);
|
||||
int listInstances(CommandState& cmd);
|
||||
int killInstances(CommandState& cmd);
|
||||
int msgInstance(CommandState& cmd);
|
||||
int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication);
|
||||
|
||||
struct LaunchArgs {
|
||||
|
@ -268,6 +279,10 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
|||
};
|
||||
|
||||
auto cli = CLI::App();
|
||||
|
||||
// Require 0-1 subcommands. Without this, positionals can be parsed as more subcommands.
|
||||
cli.require_subcommand(0, 1);
|
||||
|
||||
addConfigSelection(&cli);
|
||||
addLoggingOptions(&cli, false);
|
||||
addDebugOptions(&cli);
|
||||
|
@ -331,6 +346,34 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
|||
state.subcommand.kill = sub;
|
||||
}
|
||||
|
||||
{
|
||||
auto* sub = cli.add_subcommand("msg", "Send messages to IpcHandlers.")->require_option();
|
||||
|
||||
auto* target = sub->add_option("target", state.ipc.target, "The target to message.");
|
||||
|
||||
auto* function = sub->add_option("function", state.ipc.function)
|
||||
->description("The function to call in the target.")
|
||||
->needs(target);
|
||||
|
||||
auto* arguments = sub->add_option("arguments", state.ipc.arguments)
|
||||
->description("Arguments to the called function.")
|
||||
->needs(function)
|
||||
->allow_extra_args();
|
||||
|
||||
sub->add_flag("-i,--info", state.ipc.info)
|
||||
->description("Print information about a function or target if given, or all available "
|
||||
"targets if not.")
|
||||
->excludes(arguments);
|
||||
|
||||
auto* instance = addInstanceSelection(sub);
|
||||
addConfigSelection(sub)->excludes(instance);
|
||||
addLoggingOptions(sub, false, true);
|
||||
|
||||
sub->require_option();
|
||||
|
||||
state.subcommand.msg = sub;
|
||||
}
|
||||
|
||||
CLI11_PARSE(cli, argc, argv);
|
||||
|
||||
// Has to happen before extra threads are spawned.
|
||||
|
@ -389,6 +432,8 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
|||
return listInstances(state);
|
||||
} else if (*state.subcommand.kill) {
|
||||
return killInstances(state);
|
||||
} else if (*state.subcommand.msg) {
|
||||
return msgInstance(state);
|
||||
} else {
|
||||
return launchFromCommand(state, coreApplication);
|
||||
}
|
||||
|
@ -647,6 +692,32 @@ int killInstances(CommandState& cmd) {
|
|||
});
|
||||
}
|
||||
|
||||
int msgInstance(CommandState& cmd) {
|
||||
InstanceLockInfo instance;
|
||||
auto r = selectInstance(cmd, &instance);
|
||||
if (r != 0) return r;
|
||||
|
||||
return IpcClient::connect(instance.instance.instanceId, [&](IpcClient& client) {
|
||||
if (cmd.ipc.info) {
|
||||
return qs::io::ipc::comm::queryMetadata(&client, *cmd.ipc.target, *cmd.ipc.function);
|
||||
} else {
|
||||
QVector<QString> arguments;
|
||||
for (auto& arg: cmd.ipc.arguments) {
|
||||
arguments += *arg;
|
||||
}
|
||||
|
||||
return qs::io::ipc::comm::callFunction(
|
||||
&client,
|
||||
*cmd.ipc.target,
|
||||
*cmd.ipc.function,
|
||||
arguments
|
||||
);
|
||||
}
|
||||
|
||||
return -1;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
QString base36Encode(T number) {
|
||||
const QString digits = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
|
|
|
@ -2,6 +2,9 @@ qt_add_library(quickshell-io STATIC
|
|||
datastream.cpp
|
||||
process.cpp
|
||||
fileview.cpp
|
||||
ipccomm.cpp
|
||||
ipc.cpp
|
||||
ipchandler.cpp
|
||||
)
|
||||
|
||||
add_library(quickshell-io-init OBJECT init.cpp)
|
||||
|
|
180
src/io/ipc.cpp
Normal file
180
src/io/ipc.cpp
Normal file
|
@ -0,0 +1,180 @@
|
|||
#include "ipc.hpp"
|
||||
#include <utility>
|
||||
|
||||
#include <qcolor.h>
|
||||
#include <qmetatype.h>
|
||||
#include <qobjectdefs.h>
|
||||
|
||||
namespace qs::io::ipc {
|
||||
|
||||
const VoidIpcType VoidIpcType::INSTANCE {};
|
||||
const StringIpcType StringIpcType::INSTANCE {};
|
||||
const IntIpcType IntIpcType::INSTANCE {};
|
||||
const BoolIpcType BoolIpcType::INSTANCE {};
|
||||
const DoubleIpcType DoubleIpcType::INSTANCE {};
|
||||
const ColorIpcType ColorIpcType::INSTANCE {};
|
||||
|
||||
const IpcType* IpcType::ipcType(const QMetaType& metaType) {
|
||||
if (metaType.id() == QMetaType::Void) return &VoidIpcType::INSTANCE;
|
||||
if (metaType.id() == QMetaType::QString) return &StringIpcType::INSTANCE;
|
||||
if (metaType.id() == QMetaType::Int) return &IntIpcType::INSTANCE;
|
||||
if (metaType.id() == QMetaType::Bool) return &BoolIpcType::INSTANCE;
|
||||
if (metaType.id() == QMetaType::Double) return &DoubleIpcType::INSTANCE;
|
||||
if (metaType.id() == QMetaType::QColor) return &ColorIpcType::INSTANCE;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
IpcTypeSlot::IpcTypeSlot(IpcTypeSlot&& other) noexcept { *this = std::move(other); }
|
||||
|
||||
IpcTypeSlot& IpcTypeSlot::operator=(IpcTypeSlot&& other) noexcept {
|
||||
this->mType = other.mType;
|
||||
this->storage = other.storage;
|
||||
other.mType = nullptr;
|
||||
other.storage = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
IpcTypeSlot::~IpcTypeSlot() { this->replace(nullptr); }
|
||||
|
||||
const IpcType* IpcTypeSlot::type() const { return this->mType; }
|
||||
|
||||
void* IpcTypeSlot::get() {
|
||||
if (this->storage == nullptr) {
|
||||
this->storage = this->mType->createStorage();
|
||||
}
|
||||
|
||||
return this->storage;
|
||||
}
|
||||
|
||||
QGenericArgument IpcTypeSlot::asGenericArgument() {
|
||||
if (this->mType) {
|
||||
return QGenericArgument(this->mType->genericArgumentName(), this->get());
|
||||
} else {
|
||||
return QGenericArgument();
|
||||
}
|
||||
}
|
||||
|
||||
QGenericReturnArgument IpcTypeSlot::asGenericReturnArgument() {
|
||||
if (this->mType) {
|
||||
return QGenericReturnArgument(this->mType->genericArgumentName(), this->get());
|
||||
} else {
|
||||
return QGenericReturnArgument();
|
||||
}
|
||||
}
|
||||
|
||||
void IpcTypeSlot::replace(void* value) {
|
||||
if (this->storage != nullptr) {
|
||||
this->mType->destroyStorage(this->storage);
|
||||
}
|
||||
|
||||
this->storage = value;
|
||||
}
|
||||
|
||||
const char* VoidIpcType::name() const { return "void"; }
|
||||
const char* VoidIpcType::genericArgumentName() const { return "void"; }
|
||||
|
||||
// string
|
||||
const char* StringIpcType::name() const { return "string"; }
|
||||
const char* StringIpcType::genericArgumentName() const { return "QString"; }
|
||||
void* StringIpcType::fromString(const QString& string) const { return new QString(string); }
|
||||
QString StringIpcType::toString(void* slot) const { return *static_cast<QString*>(slot); }
|
||||
void* StringIpcType::createStorage() const { return new QString(); }
|
||||
void StringIpcType::destroyStorage(void* slot) const { delete static_cast<QString*>(slot); }
|
||||
|
||||
// int
|
||||
const char* IntIpcType::name() const { return "int"; }
|
||||
const char* IntIpcType::genericArgumentName() const { return "int"; }
|
||||
|
||||
void* IntIpcType::fromString(const QString& string) const {
|
||||
auto ok = false;
|
||||
auto v = string.toInt(&ok);
|
||||
|
||||
return ok ? new int(v) : nullptr;
|
||||
}
|
||||
|
||||
QString IntIpcType::toString(void* slot) const { return QString::number(*static_cast<int*>(slot)); }
|
||||
|
||||
void* IntIpcType::createStorage() const { return new int(); }
|
||||
void IntIpcType::destroyStorage(void* slot) const { delete static_cast<int*>(slot); }
|
||||
|
||||
// bool
|
||||
const char* BoolIpcType::name() const { return "bool"; }
|
||||
const char* BoolIpcType::genericArgumentName() const { return "bool"; }
|
||||
|
||||
void* BoolIpcType::fromString(const QString& string) const {
|
||||
if (string == "true") return new bool(true);
|
||||
if (string == "false") return new bool(false);
|
||||
|
||||
auto isInt = false;
|
||||
auto iv = string.toInt(&isInt);
|
||||
|
||||
return isInt ? new bool(iv != 0) : nullptr;
|
||||
}
|
||||
|
||||
QString BoolIpcType::toString(void* slot) const {
|
||||
return *static_cast<bool*>(slot) ? "true" : "false";
|
||||
}
|
||||
|
||||
void* BoolIpcType::createStorage() const { return new bool(); }
|
||||
void BoolIpcType::destroyStorage(void* slot) const { delete static_cast<bool*>(slot); }
|
||||
|
||||
// double
|
||||
const char* DoubleIpcType::name() const { return "real"; }
|
||||
const char* DoubleIpcType::genericArgumentName() const { return "double"; }
|
||||
|
||||
void* DoubleIpcType::fromString(const QString& string) const {
|
||||
auto ok = false;
|
||||
auto v = string.toDouble(&ok);
|
||||
|
||||
return ok ? new double(v) : nullptr;
|
||||
}
|
||||
|
||||
QString DoubleIpcType::toString(void* slot) const {
|
||||
return QString::number(*static_cast<double*>(slot));
|
||||
}
|
||||
|
||||
void* DoubleIpcType::createStorage() const { return new double(); }
|
||||
void DoubleIpcType::destroyStorage(void* slot) const { delete static_cast<double*>(slot); }
|
||||
|
||||
// color
|
||||
const char* ColorIpcType::name() const { return "color"; }
|
||||
const char* ColorIpcType::genericArgumentName() const { return "QColor"; }
|
||||
|
||||
void* ColorIpcType::fromString(const QString& string) const {
|
||||
auto color = QColor::fromString(string);
|
||||
|
||||
if (!color.isValid() && !string.startsWith('#')) {
|
||||
color = QColor::fromString('#' % string);
|
||||
}
|
||||
|
||||
return color.isValid() ? new QColor(color) : nullptr;
|
||||
}
|
||||
|
||||
QString ColorIpcType::toString(void* slot) const {
|
||||
return static_cast<QColor*>(slot)->name(QColor::HexArgb);
|
||||
}
|
||||
|
||||
void* ColorIpcType::createStorage() const { return new bool(); }
|
||||
void ColorIpcType::destroyStorage(void* slot) const { delete static_cast<bool*>(slot); }
|
||||
|
||||
QString WireFunctionDefinition::toString() const {
|
||||
QString paramString;
|
||||
for (const auto& [name, type]: this->arguments) {
|
||||
if (!paramString.isEmpty()) paramString += ", ";
|
||||
paramString += name % ": " % type;
|
||||
}
|
||||
|
||||
return "function " % this->name % '(' % paramString % "): " % this->returnType;
|
||||
}
|
||||
|
||||
QString WireTargetDefinition::toString() const {
|
||||
QString accum = "target " % this->name;
|
||||
|
||||
for (const auto& func: this->functions) {
|
||||
accum += "\n " % func.toString();
|
||||
}
|
||||
|
||||
return accum;
|
||||
}
|
||||
|
||||
} // namespace qs::io::ipc
|
139
src/io/ipc.hpp
Normal file
139
src/io/ipc.hpp
Normal file
|
@ -0,0 +1,139 @@
|
|||
#pragma once
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qobjectdefs.h>
|
||||
#include <qtclasshelpermacros.h>
|
||||
|
||||
#include "../core/ipc.hpp"
|
||||
|
||||
namespace qs::io::ipc {
|
||||
|
||||
class IpcTypeSlot;
|
||||
|
||||
class IpcType {
|
||||
public:
|
||||
IpcType() = default;
|
||||
virtual ~IpcType() = default;
|
||||
IpcType(const IpcType&) = default;
|
||||
IpcType(IpcType&&) = default;
|
||||
IpcType& operator=(const IpcType&) = default;
|
||||
IpcType& operator=(IpcType&&) = default;
|
||||
|
||||
[[nodiscard]] virtual const char* name() const = 0;
|
||||
[[nodiscard]] virtual const char* genericArgumentName() const = 0;
|
||||
[[nodiscard]] virtual void* fromString(const QString& /*string*/) const { return nullptr; }
|
||||
[[nodiscard]] virtual QString toString(void* /*slot*/) const { return ""; }
|
||||
[[nodiscard]] virtual void* createStorage() const { return nullptr; }
|
||||
virtual void destroyStorage(void* /*slot*/) const {}
|
||||
|
||||
static const IpcType* ipcType(const QMetaType& metaType);
|
||||
};
|
||||
|
||||
class IpcTypeSlot {
|
||||
public:
|
||||
explicit IpcTypeSlot(const IpcType* type = nullptr): mType(type) {}
|
||||
~IpcTypeSlot();
|
||||
Q_DISABLE_COPY(IpcTypeSlot);
|
||||
IpcTypeSlot(IpcTypeSlot&& other) noexcept;
|
||||
IpcTypeSlot& operator=(IpcTypeSlot&& other) noexcept;
|
||||
|
||||
[[nodiscard]] const IpcType* type() const;
|
||||
[[nodiscard]] void* get();
|
||||
[[nodiscard]] QGenericArgument asGenericArgument();
|
||||
[[nodiscard]] QGenericReturnArgument asGenericReturnArgument();
|
||||
|
||||
void replace(void* value);
|
||||
|
||||
private:
|
||||
const IpcType* mType = nullptr;
|
||||
void* storage = nullptr;
|
||||
};
|
||||
|
||||
class VoidIpcType: public IpcType {
|
||||
public:
|
||||
[[nodiscard]] const char* name() const override;
|
||||
[[nodiscard]] const char* genericArgumentName() const override;
|
||||
|
||||
static const VoidIpcType INSTANCE;
|
||||
};
|
||||
|
||||
class StringIpcType: public IpcType {
|
||||
public:
|
||||
[[nodiscard]] const char* name() const override;
|
||||
[[nodiscard]] const char* genericArgumentName() const override;
|
||||
[[nodiscard]] void* fromString(const QString& string) const override;
|
||||
[[nodiscard]] QString toString(void* slot) const override;
|
||||
[[nodiscard]] void* createStorage() const override;
|
||||
void destroyStorage(void* slot) const override;
|
||||
|
||||
static const StringIpcType INSTANCE;
|
||||
};
|
||||
|
||||
class IntIpcType: public IpcType {
|
||||
public:
|
||||
[[nodiscard]] const char* name() const override;
|
||||
[[nodiscard]] const char* genericArgumentName() const override;
|
||||
[[nodiscard]] void* fromString(const QString& string) const override;
|
||||
[[nodiscard]] QString toString(void* slot) const override;
|
||||
[[nodiscard]] void* createStorage() const override;
|
||||
void destroyStorage(void* slot) const override;
|
||||
|
||||
static const IntIpcType INSTANCE;
|
||||
};
|
||||
|
||||
class BoolIpcType: public IpcType {
|
||||
public:
|
||||
[[nodiscard]] const char* name() const override;
|
||||
[[nodiscard]] const char* genericArgumentName() const override;
|
||||
[[nodiscard]] void* fromString(const QString& string) const override;
|
||||
[[nodiscard]] QString toString(void* slot) const override;
|
||||
[[nodiscard]] void* createStorage() const override;
|
||||
void destroyStorage(void* slot) const override;
|
||||
|
||||
static const BoolIpcType INSTANCE;
|
||||
};
|
||||
|
||||
class DoubleIpcType: public IpcType {
|
||||
public:
|
||||
[[nodiscard]] const char* name() const override;
|
||||
[[nodiscard]] const char* genericArgumentName() const override;
|
||||
[[nodiscard]] void* fromString(const QString& string) const override;
|
||||
[[nodiscard]] QString toString(void* slot) const override;
|
||||
[[nodiscard]] void* createStorage() const override;
|
||||
void destroyStorage(void* slot) const override;
|
||||
|
||||
static const DoubleIpcType INSTANCE;
|
||||
};
|
||||
|
||||
class ColorIpcType: public IpcType {
|
||||
public:
|
||||
[[nodiscard]] const char* name() const override;
|
||||
[[nodiscard]] const char* genericArgumentName() const override;
|
||||
[[nodiscard]] void* fromString(const QString& string) const override;
|
||||
[[nodiscard]] QString toString(void* slot) const override;
|
||||
[[nodiscard]] void* createStorage() const override;
|
||||
void destroyStorage(void* slot) const override;
|
||||
|
||||
static const ColorIpcType INSTANCE;
|
||||
};
|
||||
|
||||
struct WireFunctionDefinition {
|
||||
QString name;
|
||||
QString returnType;
|
||||
QVector<QPair<QString, QString>> arguments;
|
||||
|
||||
[[nodiscard]] QString toString() const;
|
||||
};
|
||||
|
||||
DEFINE_SIMPLE_DATASTREAM_OPS(WireFunctionDefinition, data.name, data.returnType, data.arguments);
|
||||
|
||||
struct WireTargetDefinition {
|
||||
QString name;
|
||||
QVector<WireFunctionDefinition> functions;
|
||||
|
||||
[[nodiscard]] QString toString() const;
|
||||
};
|
||||
|
||||
DEFINE_SIMPLE_DATASTREAM_OPS(WireTargetDefinition, data.name, data.functions);
|
||||
|
||||
} // namespace qs::io::ipc
|
236
src/io/ipccomm.cpp
Normal file
236
src/io/ipccomm.cpp
Normal file
|
@ -0,0 +1,236 @@
|
|||
#include "ipccomm.hpp"
|
||||
#include <cstdio>
|
||||
#include <variant>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qtextstream.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../core/generation.hpp"
|
||||
#include "../core/ipc.hpp"
|
||||
#include "../core/ipccommand.hpp"
|
||||
#include "../core/logging.hpp"
|
||||
#include "ipc.hpp"
|
||||
#include "ipchandler.hpp"
|
||||
|
||||
using namespace qs::ipc;
|
||||
|
||||
namespace qs::io::ipc::comm {
|
||||
|
||||
struct NoCurrentGeneration: std::monostate {};
|
||||
struct TargetNotFound: std::monostate {};
|
||||
struct FunctionNotFound: std::monostate {};
|
||||
|
||||
using QueryResponse = std::variant<
|
||||
std::monostate,
|
||||
NoCurrentGeneration,
|
||||
TargetNotFound,
|
||||
FunctionNotFound,
|
||||
QVector<WireTargetDefinition>,
|
||||
WireTargetDefinition,
|
||||
WireFunctionDefinition>;
|
||||
|
||||
void QueryMetadataCommand::exec(qs::ipc::IpcServerConnection* conn) const {
|
||||
auto resp = conn->responseStream<QueryResponse>();
|
||||
|
||||
if (auto* generation = EngineGeneration::currentGeneration()) {
|
||||
auto* registry = IpcHandlerRegistry::forGeneration(generation);
|
||||
|
||||
if (this->target.isEmpty()) {
|
||||
resp << registry->wireTargets();
|
||||
} else {
|
||||
auto* handler = registry->findHandler(this->target);
|
||||
|
||||
if (handler) {
|
||||
if (this->function.isEmpty()) {
|
||||
resp << handler->wireDef();
|
||||
} else {
|
||||
auto* func = handler->findFunction(this->function);
|
||||
|
||||
if (func) {
|
||||
resp << func->wireDef();
|
||||
} else {
|
||||
resp << FunctionNotFound();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resp << TargetNotFound();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
resp << NoCurrentGeneration();
|
||||
}
|
||||
}
|
||||
|
||||
int queryMetadata(IpcClient* client, const QString& target, const QString& function) {
|
||||
client->sendMessage(IpcCommand(QueryMetadataCommand {.target = target, .function = function}));
|
||||
|
||||
QueryResponse slot;
|
||||
if (!client->waitForResponse(slot)) return -1;
|
||||
|
||||
if (std::holds_alternative<QVector<WireTargetDefinition>>(slot)) {
|
||||
const auto& targets = std::get<QVector<WireTargetDefinition>>(slot);
|
||||
|
||||
for (const auto& target: targets) {
|
||||
qCInfo(logBare).noquote() << target.toString();
|
||||
}
|
||||
|
||||
return 0;
|
||||
} else if (std::holds_alternative<WireTargetDefinition>(slot)) {
|
||||
qCInfo(logBare).noquote() << std::get<WireTargetDefinition>(slot).toString();
|
||||
} else if (std::holds_alternative<WireFunctionDefinition>(slot)) {
|
||||
qCInfo(logBare).noquote() << std::get<WireFunctionDefinition>(slot).toString();
|
||||
} else if (std::holds_alternative<TargetNotFound>(slot)) {
|
||||
qCCritical(logBare) << "Target not found.";
|
||||
} else if (std::holds_alternative<FunctionNotFound>(slot)) {
|
||||
qCCritical(logBare) << "Function not found.";
|
||||
} else if (std::holds_alternative<NoCurrentGeneration>(slot)) {
|
||||
qCCritical(logBare) << "Not ready to accept queries yet.";
|
||||
} else {
|
||||
qCCritical(logIpc) << "Received invalid IPC response from" << client;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct ArgParseFailed {
|
||||
WireFunctionDefinition definition;
|
||||
bool isCountMismatch = false;
|
||||
quint8 paramIndex = 0;
|
||||
};
|
||||
|
||||
DEFINE_SIMPLE_DATASTREAM_OPS(
|
||||
ArgParseFailed,
|
||||
data.definition,
|
||||
data.isCountMismatch,
|
||||
data.paramIndex
|
||||
);
|
||||
|
||||
struct Completed {
|
||||
bool isVoid = false;
|
||||
QString returnValue;
|
||||
};
|
||||
|
||||
DEFINE_SIMPLE_DATASTREAM_OPS(Completed, data.isVoid, data.returnValue);
|
||||
|
||||
using StringCallResponse = std::variant<
|
||||
std::monostate,
|
||||
NoCurrentGeneration,
|
||||
TargetNotFound,
|
||||
FunctionNotFound,
|
||||
ArgParseFailed,
|
||||
Completed>;
|
||||
|
||||
void StringCallCommand::exec(qs::ipc::IpcServerConnection* conn) const {
|
||||
auto resp = conn->responseStream<StringCallResponse>();
|
||||
|
||||
if (auto* generation = EngineGeneration::currentGeneration()) {
|
||||
auto* registry = IpcHandlerRegistry::forGeneration(generation);
|
||||
|
||||
auto* handler = registry->findHandler(this->target);
|
||||
if (!handler) {
|
||||
resp << TargetNotFound();
|
||||
return;
|
||||
}
|
||||
|
||||
auto* func = handler->findFunction(this->function);
|
||||
if (!func) {
|
||||
resp << FunctionNotFound();
|
||||
return;
|
||||
}
|
||||
|
||||
if (func->argumentTypes.length() != this->arguments.length()) {
|
||||
resp << ArgParseFailed {
|
||||
.definition = func->wireDef(),
|
||||
.isCountMismatch = true,
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto storage = IpcCallStorage(*func);
|
||||
for (auto i = 0; i < this->arguments.length(); i++) {
|
||||
if (!storage.setArgumentStr(i, this->arguments.value(i))) {
|
||||
resp << ArgParseFailed {
|
||||
.definition = func->wireDef(),
|
||||
.paramIndex = static_cast<quint8>(i),
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
func->invoke(handler, storage);
|
||||
|
||||
resp << Completed {
|
||||
.isVoid = func->returnType == &VoidIpcType::INSTANCE,
|
||||
.returnValue = storage.getReturnStr(),
|
||||
};
|
||||
} else {
|
||||
conn->respond(StringCallResponse(NoCurrentGeneration()));
|
||||
}
|
||||
}
|
||||
|
||||
int callFunction(
|
||||
IpcClient* client,
|
||||
const QString& target,
|
||||
const QString& function,
|
||||
const QVector<QString>& arguments
|
||||
) {
|
||||
if (target.isEmpty()) {
|
||||
qCCritical(logBare) << "Target required to send message.";
|
||||
return -1;
|
||||
} else if (function.isEmpty()) {
|
||||
qCCritical(logBare) << "Function required to send message.";
|
||||
return -1;
|
||||
}
|
||||
|
||||
client->sendMessage(
|
||||
IpcCommand(StringCallCommand {.target = target, .function = function, .arguments = arguments})
|
||||
);
|
||||
|
||||
StringCallResponse slot;
|
||||
if (!client->waitForResponse(slot)) return -1;
|
||||
|
||||
if (std::holds_alternative<Completed>(slot)) {
|
||||
auto& result = std::get<Completed>(slot);
|
||||
if (!result.isVoid) {
|
||||
QTextStream(stdout) << result.returnValue << Qt::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
} else if (std::holds_alternative<ArgParseFailed>(slot)) {
|
||||
auto& error = std::get<ArgParseFailed>(slot);
|
||||
|
||||
if (error.isCountMismatch) {
|
||||
auto correctCount = error.definition.arguments.length();
|
||||
|
||||
qCCritical(logBare).nospace()
|
||||
<< "Too " << (correctCount < arguments.length() ? "many" : "few")
|
||||
<< " arguments provided (" << correctCount << " required but " << arguments.length()
|
||||
<< " were provided.)";
|
||||
} else {
|
||||
const auto& provided = arguments.at(error.paramIndex);
|
||||
const auto& definition = error.definition.arguments.at(error.paramIndex);
|
||||
|
||||
qCCritical(logBare).nospace()
|
||||
<< "Unable to parse argument " << (error.paramIndex + 1) << " as " << definition.second
|
||||
<< ". Provided argument: " << provided;
|
||||
}
|
||||
|
||||
qCCritical(logBare).noquote() << "Function definition:" << error.definition.toString();
|
||||
} else if (std::holds_alternative<TargetNotFound>(slot)) {
|
||||
qCCritical(logBare) << "Target not found.";
|
||||
} else if (std::holds_alternative<FunctionNotFound>(slot)) {
|
||||
qCCritical(logBare) << "Function not found.";
|
||||
} else if (std::holds_alternative<NoCurrentGeneration>(slot)) {
|
||||
qCCritical(logBare) << "Not ready to accept queries yet.";
|
||||
} else {
|
||||
qCCritical(logIpc) << "Received invalid IPC response from" << client;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
} // namespace qs::io::ipc::comm
|
39
src/io/ipccomm.hpp
Normal file
39
src/io/ipccomm.hpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qflags.h>
|
||||
|
||||
#include "../core/ipc.hpp"
|
||||
|
||||
namespace qs::io::ipc::comm {
|
||||
|
||||
struct QueryMetadataCommand {
|
||||
QString target;
|
||||
QString function;
|
||||
|
||||
void exec(qs::ipc::IpcServerConnection* conn) const;
|
||||
};
|
||||
|
||||
DEFINE_SIMPLE_DATASTREAM_OPS(QueryMetadataCommand, data.target, data.function);
|
||||
|
||||
struct StringCallCommand {
|
||||
QString target;
|
||||
QString function;
|
||||
QVector<QString> arguments;
|
||||
|
||||
void exec(qs::ipc::IpcServerConnection* conn) const;
|
||||
};
|
||||
|
||||
DEFINE_SIMPLE_DATASTREAM_OPS(StringCallCommand, data.target, data.function, data.arguments);
|
||||
|
||||
void handleMsg(qs::ipc::IpcServerConnection* conn);
|
||||
int queryMetadata(qs::ipc::IpcClient* client, const QString& target, const QString& function);
|
||||
|
||||
int callFunction(
|
||||
qs::ipc::IpcClient* client,
|
||||
const QString& target,
|
||||
const QString& function,
|
||||
const QVector<QString>& arguments
|
||||
);
|
||||
|
||||
} // namespace qs::io::ipc::comm
|
324
src/io/ipchandler.cpp
Normal file
324
src/io/ipchandler.cpp
Normal file
|
@ -0,0 +1,324 @@
|
|||
#include "ipchandler.hpp"
|
||||
#include <cstddef>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdebug.h>
|
||||
#include <qlogging.h>
|
||||
#include <qmetaobject.h>
|
||||
#include <qobject.h>
|
||||
#include <qobjectdefs.h>
|
||||
#include <qpair.h>
|
||||
#include <qqmlinfo.h>
|
||||
#include <qstringbuilder.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../core/generation.hpp"
|
||||
#include "ipc.hpp"
|
||||
|
||||
namespace qs::io::ipc {
|
||||
|
||||
bool IpcFunction::resolve(QString& error) {
|
||||
if (this->method.parameterCount() > 10) {
|
||||
error = "Due to technical limitations, IPC functions can only have 10 arguments.";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < this->method.parameterCount(); i++) {
|
||||
const auto& metaType = this->method.parameterMetaType(i);
|
||||
const auto* type = IpcType::ipcType(metaType);
|
||||
|
||||
if (type == nullptr) {
|
||||
error = QString("Type of argument %1 (%2: %3) cannot be used across IPC.")
|
||||
.arg(i + 1)
|
||||
.arg(this->method.parameterNames().value(i))
|
||||
.arg(metaType.name());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
this->argumentTypes.append(type);
|
||||
}
|
||||
|
||||
const auto& metaType = this->method.returnMetaType();
|
||||
const auto* type = IpcType::ipcType(metaType);
|
||||
|
||||
if (type == nullptr) {
|
||||
// void and var get mixed by qml engine in return types
|
||||
if (metaType.id() == QMetaType::QVariant) type = &VoidIpcType::INSTANCE;
|
||||
|
||||
if (type == nullptr) {
|
||||
error = QString("Return type (%1) cannot be used across IPC.").arg(metaType.name());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this->returnType = type;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void IpcFunction::invoke(QObject* target, IpcCallStorage& storage) const {
|
||||
auto getArg = [&](size_t i) {
|
||||
return i < storage.argumentSlots.size() ? storage.argumentSlots.at(i).asGenericArgument()
|
||||
: QGenericArgument();
|
||||
};
|
||||
|
||||
this->method.invoke(
|
||||
target,
|
||||
storage.returnSlot.asGenericReturnArgument(),
|
||||
getArg(0),
|
||||
getArg(1),
|
||||
getArg(2),
|
||||
getArg(3),
|
||||
getArg(4),
|
||||
getArg(5),
|
||||
getArg(6),
|
||||
getArg(7),
|
||||
getArg(8),
|
||||
getArg(9)
|
||||
);
|
||||
}
|
||||
|
||||
QString IpcFunction::toString() const {
|
||||
QString paramString;
|
||||
auto paramNames = this->method.parameterNames();
|
||||
for (auto i = 0; i < this->argumentTypes.length(); i++) {
|
||||
paramString += paramNames.value(i) % ": " % this->argumentTypes.value(i)->name();
|
||||
|
||||
if (i + 1 != this->argumentTypes.length()) {
|
||||
paramString += ", ";
|
||||
}
|
||||
}
|
||||
|
||||
return "function " % this->method.name() % '(' % paramString % "): " % this->returnType->name();
|
||||
}
|
||||
|
||||
WireFunctionDefinition IpcFunction::wireDef() const {
|
||||
WireFunctionDefinition wire;
|
||||
wire.name = this->method.name();
|
||||
wire.returnType = this->returnType->name();
|
||||
|
||||
auto paramNames = this->method.parameterNames();
|
||||
for (auto i = 0; i < this->argumentTypes.length(); i++) {
|
||||
wire.arguments += qMakePair(paramNames.value(i), this->argumentTypes.value(i)->name());
|
||||
}
|
||||
|
||||
return wire;
|
||||
}
|
||||
|
||||
IpcCallStorage::IpcCallStorage(const IpcFunction& function): returnSlot(function.returnType) {
|
||||
for (const auto& arg: function.argumentTypes) {
|
||||
this->argumentSlots.emplace_back(arg);
|
||||
}
|
||||
}
|
||||
|
||||
bool IpcCallStorage::setArgumentStr(size_t i, const QString& value) {
|
||||
auto& slot = this->argumentSlots.at(i);
|
||||
|
||||
auto* data = slot.type()->fromString(value);
|
||||
slot.replace(data);
|
||||
return data != nullptr;
|
||||
}
|
||||
|
||||
QString IpcCallStorage::getReturnStr() {
|
||||
return this->returnSlot.type()->toString(this->returnSlot.get());
|
||||
}
|
||||
|
||||
IpcHandler::~IpcHandler() {
|
||||
if (this->registeredState.enabled) {
|
||||
this->targetState.enabled = false;
|
||||
this->updateRegistration(true);
|
||||
}
|
||||
}
|
||||
|
||||
void IpcHandler::onPostReload() {
|
||||
const auto& smeta = IpcHandler::staticMetaObject;
|
||||
const auto* meta = this->metaObject();
|
||||
|
||||
// Start at the first function following IpcHandler's slots,
|
||||
// which should handle inheritance on the qml side.
|
||||
for (auto i = smeta.methodCount(); i != meta->methodCount(); i++) {
|
||||
const auto& method = meta->method(i);
|
||||
if (method.methodType() != QMetaMethod::Slot) continue;
|
||||
|
||||
auto ipcFunc = IpcFunction(method);
|
||||
QString error;
|
||||
|
||||
if (!ipcFunc.resolve(error)) {
|
||||
qmlWarning(this).nospace().noquote()
|
||||
<< "Error parsing function \"" << method.name() << "\": " << error;
|
||||
} else {
|
||||
this->functionMap.insert(method.name(), ipcFunc);
|
||||
}
|
||||
}
|
||||
|
||||
this->complete = true;
|
||||
this->updateRegistration();
|
||||
|
||||
if (this->targetState.enabled && this->targetState.target.isEmpty()) {
|
||||
qmlWarning(this) << "This IPC handler is enabled but no target is set. This means it is "
|
||||
"effectively inoperable.";
|
||||
}
|
||||
}
|
||||
|
||||
IpcHandlerRegistry* IpcHandlerRegistry::forGeneration(EngineGeneration* generation) {
|
||||
static const int key = 0;
|
||||
auto* ext = generation->findExtension(&key);
|
||||
|
||||
if (!ext) {
|
||||
ext = new IpcHandlerRegistry();
|
||||
generation->registerExtension(&key, ext);
|
||||
}
|
||||
|
||||
return dynamic_cast<IpcHandlerRegistry*>(ext);
|
||||
}
|
||||
|
||||
void IpcHandler::updateRegistration(bool destroying) {
|
||||
if (!this->complete) return;
|
||||
|
||||
auto* generation = EngineGeneration::findObjectGeneration(this);
|
||||
|
||||
if (!generation) {
|
||||
if (!destroying) {
|
||||
qmlWarning(this) << "Unable to identify engine generation, cannot register.";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
auto* registry = IpcHandlerRegistry::forGeneration(generation);
|
||||
|
||||
if (this->registeredState.enabled) {
|
||||
registry->deregisterHandler(this);
|
||||
}
|
||||
|
||||
if (this->targetState.enabled && !this->targetState.target.isEmpty()) {
|
||||
registry->registerHandler(this);
|
||||
}
|
||||
}
|
||||
|
||||
bool IpcHandler::enabled() const { return this->targetState.enabled; }
|
||||
|
||||
void IpcHandler::setEnabled(bool enabled) {
|
||||
if (enabled != this->targetState.enabled) {
|
||||
this->targetState.enabled = enabled;
|
||||
emit this->enabledChanged();
|
||||
this->updateRegistration();
|
||||
}
|
||||
}
|
||||
|
||||
QString IpcHandler::target() const { return this->targetState.target; }
|
||||
|
||||
void IpcHandler::setTarget(const QString& target) {
|
||||
if (target != this->targetState.target) {
|
||||
this->targetState.target = target;
|
||||
emit this->targetChanged();
|
||||
this->updateRegistration();
|
||||
}
|
||||
}
|
||||
|
||||
void IpcHandlerRegistry::registerHandler(IpcHandler* handler) {
|
||||
// inserting a new vec if not present is the desired behavior
|
||||
auto& targetVec = this->knownHandlers[handler->targetState.target];
|
||||
targetVec.append(handler);
|
||||
|
||||
if (this->handlers.contains(handler->targetState.target)) {
|
||||
qmlWarning(handler) << "Handler was registered but will not be used because another handler "
|
||||
"is registered for target "
|
||||
<< handler->targetState.target;
|
||||
} else {
|
||||
this->handlers.insert(handler->targetState.target, handler);
|
||||
}
|
||||
|
||||
handler->registeredState = handler->targetState;
|
||||
handler->registeredState.enabled = true;
|
||||
}
|
||||
|
||||
void IpcHandlerRegistry::deregisterHandler(IpcHandler* handler) {
|
||||
auto& targetVec = this->knownHandlers[handler->registeredState.target];
|
||||
targetVec.removeOne(handler);
|
||||
|
||||
if (this->handlers.value(handler->registeredState.target) == handler) {
|
||||
if (targetVec.isEmpty()) {
|
||||
this->handlers.remove(handler->registeredState.target);
|
||||
} else {
|
||||
this->handlers.insert(handler->registeredState.target, targetVec.first());
|
||||
}
|
||||
}
|
||||
|
||||
handler->registeredState = {.enabled = false, .target = ""};
|
||||
}
|
||||
|
||||
QString IpcHandler::listMembers(qsizetype indent) {
|
||||
auto indentStr = QString(indent, ' ');
|
||||
QString accum;
|
||||
|
||||
for (const auto& func: this->functionMap.values()) {
|
||||
if (!accum.isEmpty()) accum += '\n';
|
||||
accum += indentStr % func.toString();
|
||||
}
|
||||
|
||||
return accum;
|
||||
}
|
||||
|
||||
WireTargetDefinition IpcHandler::wireDef() const {
|
||||
WireTargetDefinition wire;
|
||||
wire.name = this->registeredState.target;
|
||||
|
||||
for (const auto& func: this->functionMap.values()) {
|
||||
wire.functions += func.wireDef();
|
||||
}
|
||||
|
||||
return wire;
|
||||
}
|
||||
|
||||
QString IpcHandlerRegistry::listMembers(const QString& target, qsizetype indent) {
|
||||
if (auto* handler = this->handlers.value(target)) {
|
||||
return handler->listMembers(indent);
|
||||
} else {
|
||||
QString accum;
|
||||
|
||||
for (auto* handler: this->knownHandlers.value(target)) {
|
||||
if (!accum.isEmpty()) accum += '\n';
|
||||
accum += handler->listMembers(indent);
|
||||
}
|
||||
|
||||
return accum;
|
||||
}
|
||||
}
|
||||
|
||||
QString IpcHandlerRegistry::listTargets(qsizetype indent) {
|
||||
auto indentStr = QString(indent, ' ');
|
||||
QString accum;
|
||||
|
||||
for (const auto& target: this->knownHandlers.keys()) {
|
||||
if (!accum.isEmpty()) accum += '\n';
|
||||
accum += indentStr % "Target " % target % '\n' % this->listMembers(target, indent + 2);
|
||||
}
|
||||
|
||||
return accum;
|
||||
}
|
||||
|
||||
IpcFunction* IpcHandler::findFunction(const QString& name) {
|
||||
auto itr = this->functionMap.find(name);
|
||||
|
||||
if (itr == this->functionMap.end()) return nullptr;
|
||||
else return &*itr;
|
||||
}
|
||||
|
||||
IpcHandler* IpcHandlerRegistry::findHandler(const QString& target) {
|
||||
return this->handlers.value(target);
|
||||
}
|
||||
|
||||
QVector<WireTargetDefinition> IpcHandlerRegistry::wireTargets() const {
|
||||
QVector<WireTargetDefinition> wire;
|
||||
|
||||
for (const auto* handler: this->handlers.values()) {
|
||||
wire += handler->wireDef();
|
||||
}
|
||||
|
||||
return wire;
|
||||
}
|
||||
|
||||
} // namespace qs::io::ipc
|
207
src/io/ipchandler.hpp
Normal file
207
src/io/ipchandler.hpp
Normal file
|
@ -0,0 +1,207 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qdebug.h>
|
||||
#include <qhash.h>
|
||||
#include <qmetaobject.h>
|
||||
#include <qobject.h>
|
||||
#include <qobjectdefs.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qqmlparserstatus.h>
|
||||
#include <qtclasshelpermacros.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
#include "../core/generation.hpp"
|
||||
#include "../core/reload.hpp"
|
||||
#include "ipc.hpp"
|
||||
|
||||
namespace qs::io::ipc {
|
||||
|
||||
class IpcCallStorage;
|
||||
|
||||
class IpcFunction {
|
||||
public:
|
||||
explicit IpcFunction(QMetaMethod method): method(method) {}
|
||||
|
||||
bool resolve(QString& error);
|
||||
void invoke(QObject* target, IpcCallStorage& storage) const;
|
||||
|
||||
[[nodiscard]] QString toString() const;
|
||||
[[nodiscard]] WireFunctionDefinition wireDef() const;
|
||||
|
||||
QMetaMethod method;
|
||||
QVector<const IpcType*> argumentTypes;
|
||||
const IpcType* returnType = nullptr;
|
||||
};
|
||||
|
||||
class IpcCallStorage {
|
||||
public:
|
||||
explicit IpcCallStorage(const IpcFunction& function);
|
||||
|
||||
bool setArgumentStr(size_t i, const QString& value);
|
||||
[[nodiscard]] QString getReturnStr();
|
||||
|
||||
private:
|
||||
std::vector<IpcTypeSlot> argumentSlots;
|
||||
IpcTypeSlot returnSlot;
|
||||
|
||||
friend class IpcFunction;
|
||||
};
|
||||
|
||||
class IpcHandlerRegistry;
|
||||
|
||||
///! Handler for IPC message calls.
|
||||
/// Each IpcHandler is registered into a per-instance map by its unique @@target.
|
||||
/// Functions defined on the IpcHandler can be called by `qs msg`.
|
||||
///
|
||||
/// #### Handler Functions
|
||||
/// IPC handler functions can be called by `qs msg` as long as they have at most 10
|
||||
/// arguments, and all argument types along with the return type are listed below.
|
||||
///
|
||||
/// **Argument and return types must be explicitly specified or they will not
|
||||
/// be registered.**
|
||||
///
|
||||
/// ##### Arguments
|
||||
/// - `string` will be passed to the parameter as is.
|
||||
/// - `int` will only accept parameters that can be parsed as an integer.
|
||||
/// - `bool` will only accept parameters that are "true", "false", or an integer,
|
||||
/// where 0 will be converted to false, and anything else to true.
|
||||
/// - `real` will only accept parameters that can be parsed as a number with
|
||||
/// or without a decimal.
|
||||
/// - `color` will accept [named colors] or hex strings (RGB, RRGGBB, AARRGGBB) with
|
||||
/// an optional `#` prefix.
|
||||
///
|
||||
/// [named colors]: https://doc.qt.io/qt-6/qml-color.html#svg-color-reference
|
||||
///
|
||||
/// ##### Return Type
|
||||
/// - `void` will return nothing.
|
||||
/// - `string` will be returned as is.
|
||||
/// - `int` will be converted to a string and returned.
|
||||
/// - `bool` will be converted to "true" or "false" and returned.
|
||||
/// - `real` will be converted to a string and returned.
|
||||
/// - `color` will be converted to a hex string in the form `#AARRGGBB` and returned.
|
||||
///
|
||||
/// #### Example
|
||||
/// The following example creates ipc functions to control and retrieve the appearance
|
||||
/// of a Rectangle.
|
||||
///
|
||||
/// ```qml
|
||||
/// FloatingWindow {
|
||||
/// Rectangle {
|
||||
/// id: rect
|
||||
/// anchors.centerIn: parent
|
||||
/// width: 100
|
||||
/// height: 100
|
||||
/// color: "red"
|
||||
/// }
|
||||
///
|
||||
/// IpcHandler {
|
||||
/// target: "rect"
|
||||
///
|
||||
/// function setColor(color: color): void { rect.color = color; }
|
||||
/// function getColor(): color { return rect.color; }
|
||||
/// function setAngle(angle: real): void { rect.rotation = angle; }
|
||||
/// function getAngle(): real { return rect.rotation; }
|
||||
/// function setRadius(radius: int): void { rect.radius = radius; }
|
||||
/// function getRadius(): int { return rect.radius; }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
/// The list of registered targets can be inspected using `qs msg -i`.
|
||||
/// ```sh
|
||||
/// $ qs msg -i
|
||||
/// target rect
|
||||
/// function setColor(color: color): void
|
||||
/// function getColor(): color
|
||||
/// function setAngle(angle: real): void
|
||||
/// function getAngle(): real
|
||||
/// function setRadius(radius: int): void
|
||||
/// function getRadius(): int
|
||||
/// ```
|
||||
///
|
||||
/// and then invoked using `qs msg`.
|
||||
/// ```sh
|
||||
/// $ qs msg rect setColor orange
|
||||
/// $ qs msg rect setAngle 40.5
|
||||
/// $ qs msg rect setRadius 30
|
||||
/// $ qs msg rect getColor
|
||||
/// #ffffa500
|
||||
/// $ qs msg rect getAngle
|
||||
/// 40.5
|
||||
/// $ qs msg rect getRadius
|
||||
/// 30
|
||||
/// ```
|
||||
class IpcHandler
|
||||
: public QObject
|
||||
, public PostReloadHook {
|
||||
Q_OBJECT;
|
||||
/// If the handler should be able to receive calls. Defaults to true.
|
||||
Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged);
|
||||
/// The target this handler should be accessible from.
|
||||
/// Required and must be unique. May be changed at runtime.
|
||||
Q_PROPERTY(QString target READ target WRITE setTarget NOTIFY targetChanged);
|
||||
QML_ELEMENT;
|
||||
|
||||
public:
|
||||
explicit IpcHandler(QObject* parent = nullptr): QObject(parent) {};
|
||||
~IpcHandler() override;
|
||||
Q_DISABLE_COPY_MOVE(IpcHandler);
|
||||
|
||||
void onPostReload() override;
|
||||
|
||||
[[nodiscard]] bool enabled() const;
|
||||
void setEnabled(bool enabled);
|
||||
|
||||
[[nodiscard]] QString target() const;
|
||||
void setTarget(const QString& target);
|
||||
|
||||
QString listMembers(qsizetype indent);
|
||||
[[nodiscard]] IpcFunction* findFunction(const QString& name);
|
||||
[[nodiscard]] WireTargetDefinition wireDef() const;
|
||||
|
||||
signals:
|
||||
void enabledChanged();
|
||||
void targetChanged();
|
||||
|
||||
private:
|
||||
void updateRegistration(bool destroying = false);
|
||||
|
||||
struct RegistrationState {
|
||||
bool enabled = false;
|
||||
QString target;
|
||||
};
|
||||
|
||||
RegistrationState registeredState;
|
||||
RegistrationState targetState {.enabled = true};
|
||||
bool complete = false;
|
||||
|
||||
QHash<QString, IpcFunction> functionMap;
|
||||
|
||||
friend class IpcHandlerRegistry;
|
||||
};
|
||||
|
||||
class IpcHandlerRegistry: public EngineGenerationExt {
|
||||
public:
|
||||
static IpcHandlerRegistry* forGeneration(EngineGeneration* generation);
|
||||
|
||||
void registerHandler(IpcHandler* handler);
|
||||
void deregisterHandler(IpcHandler* handler);
|
||||
|
||||
QString listMembers(const QString& target, qsizetype indent);
|
||||
QString listTargets(qsizetype indent);
|
||||
|
||||
IpcHandler* findHandler(const QString& target);
|
||||
|
||||
[[nodiscard]] QVector<WireTargetDefinition> wireTargets() const;
|
||||
|
||||
private:
|
||||
QHash<QString, IpcHandler*> handlers;
|
||||
QHash<QString, QVector<IpcHandler*>> knownHandlers;
|
||||
};
|
||||
|
||||
} // namespace qs::io::ipc
|
|
@ -5,5 +5,6 @@ headers = [
|
|||
"socket.hpp",
|
||||
"process.hpp",
|
||||
"fileview.hpp",
|
||||
"ipchandler.hpp",
|
||||
]
|
||||
-----
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue