#include "ipchandler.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #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(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 IpcHandlerRegistry::wireTargets() const { QVector wire; for (const auto* handler: this->handlers.values()) { wire += handler->wireDef(); } return wire; } } // namespace qs::io::ipc