forked from quickshell/quickshell
385 lines
10 KiB
C++
385 lines
10 KiB
C++
#include "ipchandler.hpp"
|
|
#include <cstddef>
|
|
|
|
#include <qcontainerfwd.h>
|
|
#include <qdebug.h>
|
|
#include <qlogging.h>
|
|
#include <qloggingcategory.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 "../core/logcat.hpp"
|
|
#include "ipc.hpp"
|
|
|
|
namespace qs::io::ipc {
|
|
|
|
namespace {
|
|
QS_LOGGING_CATEGORY(logIpcHandler, "quickshell.ipchandler", QtWarningMsg)
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool IpcProperty::resolve(QString& error) {
|
|
this->type = IpcType::ipcType(this->property.metaType());
|
|
|
|
if (!this->type) {
|
|
error = QString("Type %1 cannot be used across IPC.").arg(this->property.metaType().name());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void IpcProperty::read(QObject* target, IpcTypeSlot& slot) const {
|
|
slot.replace(this->property.read(target));
|
|
}
|
|
|
|
QString IpcProperty::toString() const {
|
|
return QString("property ") % this->property.name() % ": " % this->type->name();
|
|
}
|
|
|
|
WirePropertyDefinition IpcProperty::wireDef() const {
|
|
WirePropertyDefinition wire;
|
|
wire.name = this->property.name();
|
|
wire.type = this->type->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);
|
|
}
|
|
}
|
|
|
|
for (auto i = smeta.propertyCount(); i != meta->propertyCount(); i++) {
|
|
const auto& property = meta->property(i);
|
|
if (!property.isReadable() || !property.hasNotifySignal()) continue;
|
|
|
|
auto ipcProp = IpcProperty(property);
|
|
QString error;
|
|
|
|
if (!ipcProp.resolve(error)) {
|
|
qmlWarning(this).nospace().noquote()
|
|
<< "Error parsing property \"" << property.name() << "\": " << error;
|
|
} else {
|
|
this->propertyMap.insert(property.name(), ipcProp);
|
|
}
|
|
}
|
|
|
|
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);
|
|
qCDebug(logIpcHandler) << "Created new IPC handler registry" << ext << "for" << generation;
|
|
}
|
|
|
|
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);
|
|
qCDebug(logIpcHandler) << "Deregistered" << this << "from registry" << registry;
|
|
}
|
|
|
|
if (this->targetState.enabled && !this->targetState.target.isEmpty()) {
|
|
registry->registerHandler(this);
|
|
qCDebug(logIpcHandler) << "Registered" << this << "to registry" << registry;
|
|
}
|
|
}
|
|
|
|
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 = IpcHandler::RegistrationState(false);
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
for (const auto& prop: this->propertyMap.values()) {
|
|
wire.properties += prop.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;
|
|
}
|
|
|
|
IpcProperty* IpcHandler::findProperty(const QString& name) {
|
|
auto itr = this->propertyMap.find(name);
|
|
|
|
if (itr == this->propertyMap.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
|