io/ipchandler: add IpcHandler and qs msg

Also reworks the whole ipc system to use serialized variants.
This commit is contained in:
outfoxxed 2024-09-13 02:44:33 -07:00
parent 3690812919
commit 5e2fb14551
Signed by: outfoxxed
GPG key ID: 4C88A185FB89301E
14 changed files with 1428 additions and 27 deletions

View file

@ -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
View 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
View 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
View 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
View 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
View 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
View 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

View file

@ -5,5 +5,6 @@ headers = [
"socket.hpp",
"process.hpp",
"fileview.hpp",
"ipchandler.hpp",
]
-----