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