forked from quickshell/quickshell
		
	io/ipchandler: add IpcHandler and qs msg
Also reworks the whole ipc system to use serialized variants.
This commit is contained in:
		
							parent
							
								
									3690812919
								
							
						
					
					
						commit
						5e2fb14551
					
				
					 14 changed files with 1428 additions and 27 deletions
				
			
		| 
						 | 
					@ -67,6 +67,10 @@ void EngineGeneration::destroy() {
 | 
				
			||||||
		this->watcher = nullptr;
 | 
							this->watcher = nullptr;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (auto* extension: this->extensions.values()) {
 | 
				
			||||||
 | 
							delete extension;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (this->root != nullptr) {
 | 
						if (this->root != nullptr) {
 | 
				
			||||||
		QObject::connect(this->root, &QObject::destroyed, this, [this]() {
 | 
							QObject::connect(this->root, &QObject::destroyed, this, [this]() {
 | 
				
			||||||
			// prevent further js execution between garbage collection and engine destruction.
 | 
								// prevent further js execution between garbage collection and engine destruction.
 | 
				
			||||||
| 
						 | 
					@ -285,6 +289,18 @@ void EngineGeneration::incubationControllerDestroyed() {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void EngineGeneration::registerExtension(const void* key, EngineGenerationExt* extension) {
 | 
				
			||||||
 | 
						if (this->extensions.contains(key)) {
 | 
				
			||||||
 | 
							delete this->extensions.value(key);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->extensions.insert(key, extension);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					EngineGenerationExt* EngineGeneration::findExtension(const void* key) {
 | 
				
			||||||
 | 
						return this->extensions.value(key);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void EngineGeneration::quit() {
 | 
					void EngineGeneration::quit() {
 | 
				
			||||||
	this->shouldTerminate = true;
 | 
						this->shouldTerminate = true;
 | 
				
			||||||
	this->destroy();
 | 
						this->destroy();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,7 @@
 | 
				
			||||||
#include <qcontainerfwd.h>
 | 
					#include <qcontainerfwd.h>
 | 
				
			||||||
#include <qdir.h>
 | 
					#include <qdir.h>
 | 
				
			||||||
#include <qfilesystemwatcher.h>
 | 
					#include <qfilesystemwatcher.h>
 | 
				
			||||||
 | 
					#include <qhash.h>
 | 
				
			||||||
#include <qicon.h>
 | 
					#include <qicon.h>
 | 
				
			||||||
#include <qobject.h>
 | 
					#include <qobject.h>
 | 
				
			||||||
#include <qpair.h>
 | 
					#include <qpair.h>
 | 
				
			||||||
| 
						 | 
					@ -20,6 +21,13 @@
 | 
				
			||||||
class RootWrapper;
 | 
					class RootWrapper;
 | 
				
			||||||
class QuickshellGlobal;
 | 
					class QuickshellGlobal;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class EngineGenerationExt {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						EngineGenerationExt() = default;
 | 
				
			||||||
 | 
						virtual ~EngineGenerationExt() = default;
 | 
				
			||||||
 | 
						Q_DISABLE_COPY_MOVE(EngineGenerationExt);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EngineGeneration: public QObject {
 | 
					class EngineGeneration: public QObject {
 | 
				
			||||||
	Q_OBJECT;
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,6 +43,10 @@ public:
 | 
				
			||||||
	void registerIncubationController(QQmlIncubationController* controller);
 | 
						void registerIncubationController(QQmlIncubationController* controller);
 | 
				
			||||||
	void deregisterIncubationController(QQmlIncubationController* controller);
 | 
						void deregisterIncubationController(QQmlIncubationController* controller);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// takes ownership
 | 
				
			||||||
 | 
						void registerExtension(const void* key, EngineGenerationExt* extension);
 | 
				
			||||||
 | 
						EngineGenerationExt* findExtension(const void* key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	static EngineGeneration* findEngineGeneration(QQmlEngine* engine);
 | 
						static EngineGeneration* findEngineGeneration(QQmlEngine* engine);
 | 
				
			||||||
	static EngineGeneration* findObjectGeneration(QObject* object);
 | 
						static EngineGeneration* findObjectGeneration(QObject* object);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -78,6 +90,7 @@ private:
 | 
				
			||||||
	void postReload();
 | 
						void postReload();
 | 
				
			||||||
	void assignIncubationController();
 | 
						void assignIncubationController();
 | 
				
			||||||
	QVector<QPair<QQmlIncubationController*, QObject*>> incubationControllers;
 | 
						QVector<QPair<QQmlIncubationController*, QObject*>> incubationControllers;
 | 
				
			||||||
 | 
						QHash<const void*, EngineGenerationExt*> extensions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool destroying = false;
 | 
						bool destroying = false;
 | 
				
			||||||
	bool shouldTerminate = false;
 | 
						bool shouldTerminate = false;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,8 @@
 | 
				
			||||||
#include "ipc.hpp"
 | 
					#include "ipc.hpp"
 | 
				
			||||||
#include <functional>
 | 
					#include <functional>
 | 
				
			||||||
 | 
					#include <variant>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qbuffer.h>
 | 
				
			||||||
#include <qlocalserver.h>
 | 
					#include <qlocalserver.h>
 | 
				
			||||||
#include <qlocalsocket.h>
 | 
					#include <qlocalsocket.h>
 | 
				
			||||||
#include <qlogging.h>
 | 
					#include <qlogging.h>
 | 
				
			||||||
| 
						 | 
					@ -8,6 +10,7 @@
 | 
				
			||||||
#include <qobject.h>
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "generation.hpp"
 | 
					#include "generation.hpp"
 | 
				
			||||||
 | 
					#include "ipccommand.hpp"
 | 
				
			||||||
#include "paths.hpp"
 | 
					#include "paths.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace qs::ipc {
 | 
					namespace qs::ipc {
 | 
				
			||||||
| 
						 | 
					@ -62,20 +65,21 @@ void IpcServerConnection::onReadyRead() {
 | 
				
			||||||
	this->stream.startTransaction();
 | 
						this->stream.startTransaction();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this->stream.startTransaction();
 | 
						this->stream.startTransaction();
 | 
				
			||||||
	auto command = IpcCommand::Unknown;
 | 
						IpcCommand command;
 | 
				
			||||||
	this->stream >> command;
 | 
						this->stream >> command;
 | 
				
			||||||
	if (!this->stream.commitTransaction()) return;
 | 
						if (!this->stream.commitTransaction()) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	switch (command) {
 | 
						std::visit(
 | 
				
			||||||
	case IpcCommand::Kill:
 | 
						    [this]<typename Command>(Command& command) {
 | 
				
			||||||
		qInfo() << "Exiting due to IPC request.";
 | 
							    if constexpr (std::is_same_v<std::monostate, Command>) {
 | 
				
			||||||
		EngineGeneration::currentGeneration()->quit();
 | 
					 | 
				
			||||||
		break;
 | 
					 | 
				
			||||||
	default:
 | 
					 | 
				
			||||||
			    qCCritical(logIpc) << "Received invalid IPC command from" << this;
 | 
								    qCCritical(logIpc) << "Received invalid IPC command from" << this;
 | 
				
			||||||
			    this->socket->disconnectFromServer();
 | 
								    this->socket->disconnectFromServer();
 | 
				
			||||||
		break;
 | 
							    } else {
 | 
				
			||||||
 | 
								    command.exec(this);
 | 
				
			||||||
		    }
 | 
							    }
 | 
				
			||||||
 | 
						    },
 | 
				
			||||||
 | 
						    command
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!this->stream.commitTransaction()) return;
 | 
						if (!this->stream.commitTransaction()) return;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -94,11 +98,7 @@ bool IpcClient::isConnected() const { return this->socket.isValid(); }
 | 
				
			||||||
void IpcClient::waitForConnected() { this->socket.waitForConnected(); }
 | 
					void IpcClient::waitForConnected() { this->socket.waitForConnected(); }
 | 
				
			||||||
void IpcClient::waitForDisconnected() { this->socket.waitForDisconnected(); }
 | 
					void IpcClient::waitForDisconnected() { this->socket.waitForDisconnected(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void IpcClient::kill() {
 | 
					void IpcClient::kill() { this->sendMessage(IpcCommand(IpcKillCommand())); }
 | 
				
			||||||
	qCDebug(logIpc) << "Sending kill command...";
 | 
					 | 
				
			||||||
	this->stream << IpcCommand::Kill;
 | 
					 | 
				
			||||||
	this->socket.flush();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void IpcClient::onError(QLocalSocket::LocalSocketError error) {
 | 
					void IpcClient::onError(QLocalSocket::LocalSocketError error) {
 | 
				
			||||||
	qCCritical(logIpc) << "Socket Error" << error;
 | 
						qCCritical(logIpc) << "Socket Error" << error;
 | 
				
			||||||
| 
						 | 
					@ -116,4 +116,10 @@ int IpcClient::connect(const QString& id, const std::function<void(IpcClient& cl
 | 
				
			||||||
	callback(client);
 | 
						callback(client);
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void IpcKillCommand::exec(IpcServerConnection* /*unused*/) {
 | 
				
			||||||
 | 
						qInfo() << "Exiting due to IPC request.";
 | 
				
			||||||
 | 
						EngineGeneration::currentGeneration()->quit();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} // namespace qs::ipc
 | 
					} // namespace qs::ipc
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										168
									
								
								src/core/ipc.hpp
									
										
									
									
									
								
							
							
						
						
									
										168
									
								
								src/core/ipc.hpp
									
										
									
									
									
								
							| 
						 | 
					@ -1,17 +1,133 @@
 | 
				
			||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cmath>
 | 
				
			||||||
#include <functional>
 | 
					#include <functional>
 | 
				
			||||||
 | 
					#include <limits>
 | 
				
			||||||
 | 
					#include <utility>
 | 
				
			||||||
 | 
					#include <variant>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qflags.h>
 | 
				
			||||||
#include <qlocalserver.h>
 | 
					#include <qlocalserver.h>
 | 
				
			||||||
#include <qlocalsocket.h>
 | 
					#include <qlocalsocket.h>
 | 
				
			||||||
 | 
					#include <qlogging.h>
 | 
				
			||||||
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
#include <qobject.h>
 | 
					#include <qobject.h>
 | 
				
			||||||
#include <qtmetamacros.h>
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename... Types>
 | 
				
			||||||
 | 
					constexpr void assertSerializable() {
 | 
				
			||||||
 | 
						// monostate being zero ensures transactional reads wont break
 | 
				
			||||||
 | 
						static_assert(
 | 
				
			||||||
 | 
						    std::is_same_v<std::variant_alternative_t<0, std::variant<Types...>>, std::monostate>,
 | 
				
			||||||
 | 
						    "Serialization of variants without std::monostate at index 0 is disallowed."
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static_assert(
 | 
				
			||||||
 | 
						    sizeof...(Types) <= std::numeric_limits<quint8>::max(),
 | 
				
			||||||
 | 
						    "Serialization of variants that can't fit the tag in a uint8 is disallowed."
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename... Types>
 | 
				
			||||||
 | 
					QDataStream& operator<<(QDataStream& stream, const std::variant<Types...>& variant) {
 | 
				
			||||||
 | 
						assertSerializable<Types...>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (variant.valueless_by_exception()) {
 | 
				
			||||||
 | 
							stream << static_cast<quint8>(0); // must be monostate
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							stream << static_cast<quint8>(variant.index());
 | 
				
			||||||
 | 
							std::visit([&]<typename T>(const T& value) { stream << value; }, variant);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return stream;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename... Types>
 | 
				
			||||||
 | 
					constexpr bool forEachTypeIndex(const auto& f) {
 | 
				
			||||||
 | 
						return [&]<size_t... Index>(std::index_sequence<Index...>) {
 | 
				
			||||||
 | 
							return (f(std::in_place_index_t<Index>()) || ...);
 | 
				
			||||||
 | 
						}(std::index_sequence_for<Types...>());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename... Types>
 | 
				
			||||||
 | 
					std::variant<Types...> createIndexedOrMonostate(size_t index, std::variant<Types...>& variant) {
 | 
				
			||||||
 | 
						assertSerializable<Types...>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const auto initialized =
 | 
				
			||||||
 | 
						    forEachTypeIndex<Types...>([index, &variant]<size_t Index>(std::in_place_index_t<Index>) {
 | 
				
			||||||
 | 
							    if (index == Index) {
 | 
				
			||||||
 | 
								    variant.template emplace<Index>();
 | 
				
			||||||
 | 
								    return true;
 | 
				
			||||||
 | 
							    } else {
 | 
				
			||||||
 | 
								    return false;
 | 
				
			||||||
 | 
							    }
 | 
				
			||||||
 | 
						    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!initialized) {
 | 
				
			||||||
 | 
							variant = std::monostate();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return variant;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename... Types>
 | 
				
			||||||
 | 
					QDataStream& operator>>(QDataStream& stream, std::variant<Types...>& variant) {
 | 
				
			||||||
 | 
						assertSerializable<Types...>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						quint8 index = 0;
 | 
				
			||||||
 | 
						stream >> index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						createIndexedOrMonostate<Types...>(index, variant);
 | 
				
			||||||
 | 
						std::visit([&]<typename T>(T& value) { stream >> value; }, variant);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return stream;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename... Types>
 | 
				
			||||||
 | 
					QDataStream& streamInValues(QDataStream& stream, const Types&... types) {
 | 
				
			||||||
 | 
						return (stream << ... << types);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					template <typename... Types>
 | 
				
			||||||
 | 
					QDataStream& streamOutValues(QDataStream& stream, Types&... types) {
 | 
				
			||||||
 | 
						return (stream >> ... >> types);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NOLINTBEGIN
 | 
				
			||||||
 | 
					#define DEFINE_SIMPLE_DATASTREAM_OPS(Type, ...)                                                    \
 | 
				
			||||||
 | 
						inline QDataStream& operator<<(QDataStream& stream, const Type& __VA_OPT__(data)) {              \
 | 
				
			||||||
 | 
							return streamInValues(stream __VA_OPT__(, __VA_ARGS__));                                       \
 | 
				
			||||||
 | 
						}                                                                                                \
 | 
				
			||||||
 | 
					                                                                                                   \
 | 
				
			||||||
 | 
						inline QDataStream& operator>>(QDataStream& stream, Type& __VA_OPT__(data)) {                    \
 | 
				
			||||||
 | 
							return streamOutValues(stream __VA_OPT__(, __VA_ARGS__));                                      \
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					// NOLINTEND
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEFINE_SIMPLE_DATASTREAM_OPS(std::monostate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace qs::ipc {
 | 
					namespace qs::ipc {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum class IpcCommand : quint8 {
 | 
					Q_DECLARE_LOGGING_CATEGORY(logIpc);
 | 
				
			||||||
	Unknown = 0,
 | 
					
 | 
				
			||||||
	Kill,
 | 
					template <typename T>
 | 
				
			||||||
 | 
					class MessageStream {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit MessageStream(QDataStream* stream, QLocalSocket* socket)
 | 
				
			||||||
 | 
						    : stream(stream)
 | 
				
			||||||
 | 
						    , socket(socket) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						template <typename V>
 | 
				
			||||||
 | 
						MessageStream& operator<<(V value) {
 | 
				
			||||||
 | 
							*this->stream << T(value);
 | 
				
			||||||
 | 
							this->socket->flush();
 | 
				
			||||||
 | 
							return *this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						QDataStream* stream;
 | 
				
			||||||
 | 
						QLocalSocket* socket;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class IpcServer: public QObject {
 | 
					class IpcServer: public QObject {
 | 
				
			||||||
| 
						 | 
					@ -35,13 +151,24 @@ class IpcServerConnection: public QObject {
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	explicit IpcServerConnection(QLocalSocket* socket, IpcServer* server);
 | 
						explicit IpcServerConnection(QLocalSocket* socket, IpcServer* server);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						template <typename T>
 | 
				
			||||||
 | 
						void respond(const T& message) {
 | 
				
			||||||
 | 
							this->stream << message;
 | 
				
			||||||
 | 
							this->socket->flush();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						template <typename T>
 | 
				
			||||||
 | 
						MessageStream<T> responseStream() {
 | 
				
			||||||
 | 
							return MessageStream<T>(&this->stream, this->socket);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// public for access by nonlocal handlers
 | 
				
			||||||
 | 
						QLocalSocket* socket;
 | 
				
			||||||
 | 
						QDataStream stream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private slots:
 | 
					private slots:
 | 
				
			||||||
	void onDisconnected();
 | 
						void onDisconnected();
 | 
				
			||||||
	void onReadyRead();
 | 
						void onReadyRead();
 | 
				
			||||||
 | 
					 | 
				
			||||||
private:
 | 
					 | 
				
			||||||
	QLocalSocket* socket;
 | 
					 | 
				
			||||||
	QDataStream stream;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class IpcClient: public QObject {
 | 
					class IpcClient: public QObject {
 | 
				
			||||||
| 
						 | 
					@ -56,19 +183,38 @@ public:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void kill();
 | 
						void kill();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						template <typename T>
 | 
				
			||||||
 | 
						void sendMessage(const T& message) {
 | 
				
			||||||
 | 
							this->stream << message;
 | 
				
			||||||
 | 
							this->socket.flush();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						template <typename T>
 | 
				
			||||||
 | 
						bool waitForResponse(T& slot) {
 | 
				
			||||||
 | 
							while (this->socket.waitForReadyRead(-1)) {
 | 
				
			||||||
 | 
								this->stream.startTransaction();
 | 
				
			||||||
 | 
								this->stream >> slot;
 | 
				
			||||||
 | 
								if (!this->stream.commitTransaction()) continue;
 | 
				
			||||||
 | 
								return true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							qCCritical(logIpc) << "Error occurred while waiting for response.";
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	[[nodiscard]] static int
 | 
						[[nodiscard]] static int
 | 
				
			||||||
	connect(const QString& id, const std::function<void(IpcClient& client)>& callback);
 | 
						connect(const QString& id, const std::function<void(IpcClient& client)>& callback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// public for access by nonlocal handlers
 | 
				
			||||||
 | 
						QLocalSocket socket;
 | 
				
			||||||
 | 
						QDataStream stream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
signals:
 | 
					signals:
 | 
				
			||||||
	void connected();
 | 
						void connected();
 | 
				
			||||||
	void disconnected();
 | 
						void disconnected();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private slots:
 | 
					private slots:
 | 
				
			||||||
	static void onError(QLocalSocket::LocalSocketError error);
 | 
						static void onError(QLocalSocket::LocalSocketError error);
 | 
				
			||||||
 | 
					 | 
				
			||||||
private:
 | 
					 | 
				
			||||||
	QLocalSocket socket;
 | 
					 | 
				
			||||||
	QDataStream stream;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} // namespace qs::ipc
 | 
					} // namespace qs::ipc
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										20
									
								
								src/core/ipccommand.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/core/ipccommand.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,20 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <variant>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../io/ipccomm.hpp"
 | 
				
			||||||
 | 
					#include "ipc.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace qs::ipc {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct IpcKillCommand: std::monostate {
 | 
				
			||||||
 | 
						static void exec(IpcServerConnection* /*unused*/);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using IpcCommand = std::variant<
 | 
				
			||||||
 | 
					    std::monostate,
 | 
				
			||||||
 | 
					    IpcKillCommand,
 | 
				
			||||||
 | 
					    qs::io::ipc::comm::QueryMetadataCommand,
 | 
				
			||||||
 | 
					    qs::io::ipc::comm::StringCallCommand>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace qs::ipc
 | 
				
			||||||
| 
						 | 
					@ -6,6 +6,7 @@
 | 
				
			||||||
#include <cstdlib>
 | 
					#include <cstdlib>
 | 
				
			||||||
#include <limits>
 | 
					#include <limits>
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <CLI/App.hpp>
 | 
					#include <CLI/App.hpp>
 | 
				
			||||||
#include <CLI/CLI.hpp> // NOLINT: Need to include this for impls of some CLI11 classes
 | 
					#include <CLI/CLI.hpp> // NOLINT: Need to include this for impls of some CLI11 classes
 | 
				
			||||||
| 
						 | 
					@ -37,6 +38,7 @@
 | 
				
			||||||
#include <qtextstream.h>
 | 
					#include <qtextstream.h>
 | 
				
			||||||
#include <unistd.h>
 | 
					#include <unistd.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../io/ipccomm.hpp"
 | 
				
			||||||
#include "build.hpp"
 | 
					#include "build.hpp"
 | 
				
			||||||
#include "common.hpp"
 | 
					#include "common.hpp"
 | 
				
			||||||
#include "instanceinfo.hpp"
 | 
					#include "instanceinfo.hpp"
 | 
				
			||||||
| 
						 | 
					@ -157,10 +159,18 @@ struct CommandState {
 | 
				
			||||||
		bool json = false;
 | 
							bool json = false;
 | 
				
			||||||
	} output;
 | 
						} output;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct {
 | 
				
			||||||
 | 
							bool info = false;
 | 
				
			||||||
 | 
							QStringOption target;
 | 
				
			||||||
 | 
							QStringOption function;
 | 
				
			||||||
 | 
							std::vector<QStringOption> arguments;
 | 
				
			||||||
 | 
						} ipc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	struct {
 | 
						struct {
 | 
				
			||||||
		CLI::App* log = nullptr;
 | 
							CLI::App* log = nullptr;
 | 
				
			||||||
		CLI::App* list = nullptr;
 | 
							CLI::App* list = nullptr;
 | 
				
			||||||
		CLI::App* kill = nullptr;
 | 
							CLI::App* kill = nullptr;
 | 
				
			||||||
 | 
							CLI::App* msg = nullptr;
 | 
				
			||||||
	} subcommand;
 | 
						} subcommand;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	struct {
 | 
						struct {
 | 
				
			||||||
| 
						 | 
					@ -174,6 +184,7 @@ struct CommandState {
 | 
				
			||||||
int readLogFile(CommandState& cmd);
 | 
					int readLogFile(CommandState& cmd);
 | 
				
			||||||
int listInstances(CommandState& cmd);
 | 
					int listInstances(CommandState& cmd);
 | 
				
			||||||
int killInstances(CommandState& cmd);
 | 
					int killInstances(CommandState& cmd);
 | 
				
			||||||
 | 
					int msgInstance(CommandState& cmd);
 | 
				
			||||||
int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication);
 | 
					int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct LaunchArgs {
 | 
					struct LaunchArgs {
 | 
				
			||||||
| 
						 | 
					@ -268,6 +279,10 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto cli = CLI::App();
 | 
						auto cli = CLI::App();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Require 0-1 subcommands. Without this, positionals can be parsed as more subcommands.
 | 
				
			||||||
 | 
						cli.require_subcommand(0, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	addConfigSelection(&cli);
 | 
						addConfigSelection(&cli);
 | 
				
			||||||
	addLoggingOptions(&cli, false);
 | 
						addLoggingOptions(&cli, false);
 | 
				
			||||||
	addDebugOptions(&cli);
 | 
						addDebugOptions(&cli);
 | 
				
			||||||
| 
						 | 
					@ -331,6 +346,34 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
 | 
				
			||||||
		state.subcommand.kill = sub;
 | 
							state.subcommand.kill = sub;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto* sub = cli.add_subcommand("msg", "Send messages to IpcHandlers.")->require_option();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto* target = sub->add_option("target", state.ipc.target, "The target to message.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto* function = sub->add_option("function", state.ipc.function)
 | 
				
			||||||
 | 
							                     ->description("The function to call in the target.")
 | 
				
			||||||
 | 
							                     ->needs(target);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto* arguments = sub->add_option("arguments", state.ipc.arguments)
 | 
				
			||||||
 | 
							                      ->description("Arguments to the called function.")
 | 
				
			||||||
 | 
							                      ->needs(function)
 | 
				
			||||||
 | 
							                      ->allow_extra_args();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							sub->add_flag("-i,--info", state.ipc.info)
 | 
				
			||||||
 | 
							    ->description("Print information about a function or target if given, or all available "
 | 
				
			||||||
 | 
							                  "targets if not.")
 | 
				
			||||||
 | 
							    ->excludes(arguments);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto* instance = addInstanceSelection(sub);
 | 
				
			||||||
 | 
							addConfigSelection(sub)->excludes(instance);
 | 
				
			||||||
 | 
							addLoggingOptions(sub, false, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							sub->require_option();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							state.subcommand.msg = sub;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	CLI11_PARSE(cli, argc, argv);
 | 
						CLI11_PARSE(cli, argc, argv);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Has to happen before extra threads are spawned.
 | 
						// Has to happen before extra threads are spawned.
 | 
				
			||||||
| 
						 | 
					@ -389,6 +432,8 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
 | 
				
			||||||
		return listInstances(state);
 | 
							return listInstances(state);
 | 
				
			||||||
	} else if (*state.subcommand.kill) {
 | 
						} else if (*state.subcommand.kill) {
 | 
				
			||||||
		return killInstances(state);
 | 
							return killInstances(state);
 | 
				
			||||||
 | 
						} else if (*state.subcommand.msg) {
 | 
				
			||||||
 | 
							return msgInstance(state);
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		return launchFromCommand(state, coreApplication);
 | 
							return launchFromCommand(state, coreApplication);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -647,6 +692,32 @@ int killInstances(CommandState& cmd) {
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int msgInstance(CommandState& cmd) {
 | 
				
			||||||
 | 
						InstanceLockInfo instance;
 | 
				
			||||||
 | 
						auto r = selectInstance(cmd, &instance);
 | 
				
			||||||
 | 
						if (r != 0) return r;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return IpcClient::connect(instance.instance.instanceId, [&](IpcClient& client) {
 | 
				
			||||||
 | 
							if (cmd.ipc.info) {
 | 
				
			||||||
 | 
								return qs::io::ipc::comm::queryMetadata(&client, *cmd.ipc.target, *cmd.ipc.function);
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								QVector<QString> arguments;
 | 
				
			||||||
 | 
								for (auto& arg: cmd.ipc.arguments) {
 | 
				
			||||||
 | 
									arguments += *arg;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return qs::io::ipc::comm::callFunction(
 | 
				
			||||||
 | 
								    &client,
 | 
				
			||||||
 | 
								    *cmd.ipc.target,
 | 
				
			||||||
 | 
								    *cmd.ipc.function,
 | 
				
			||||||
 | 
								    arguments
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return -1;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
template <typename T>
 | 
					template <typename T>
 | 
				
			||||||
QString base36Encode(T number) {
 | 
					QString base36Encode(T number) {
 | 
				
			||||||
	const QString digits = "0123456789abcdefghijklmnopqrstuvwxyz";
 | 
						const QString digits = "0123456789abcdefghijklmnopqrstuvwxyz";
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,9 @@ qt_add_library(quickshell-io STATIC
 | 
				
			||||||
	datastream.cpp
 | 
						datastream.cpp
 | 
				
			||||||
	process.cpp
 | 
						process.cpp
 | 
				
			||||||
	fileview.cpp
 | 
						fileview.cpp
 | 
				
			||||||
 | 
						ipccomm.cpp
 | 
				
			||||||
 | 
						ipc.cpp
 | 
				
			||||||
 | 
						ipchandler.cpp
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
add_library(quickshell-io-init OBJECT init.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",
 | 
						"socket.hpp",
 | 
				
			||||||
	"process.hpp",
 | 
						"process.hpp",
 | 
				
			||||||
	"fileview.hpp",
 | 
						"fileview.hpp",
 | 
				
			||||||
 | 
						"ipchandler.hpp",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
-----
 | 
					-----
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue