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;
|
||||
}
|
||||
|
||||
for (auto* extension: this->extensions.values()) {
|
||||
delete extension;
|
||||
}
|
||||
|
||||
if (this->root != nullptr) {
|
||||
QObject::connect(this->root, &QObject::destroyed, this, [this]() {
|
||||
// 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() {
|
||||
this->shouldTerminate = true;
|
||||
this->destroy();
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <qcontainerfwd.h>
|
||||
#include <qdir.h>
|
||||
#include <qfilesystemwatcher.h>
|
||||
#include <qhash.h>
|
||||
#include <qicon.h>
|
||||
#include <qobject.h>
|
||||
#include <qpair.h>
|
||||
|
@ -20,6 +21,13 @@
|
|||
class RootWrapper;
|
||||
class QuickshellGlobal;
|
||||
|
||||
class EngineGenerationExt {
|
||||
public:
|
||||
EngineGenerationExt() = default;
|
||||
virtual ~EngineGenerationExt() = default;
|
||||
Q_DISABLE_COPY_MOVE(EngineGenerationExt);
|
||||
};
|
||||
|
||||
class EngineGeneration: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
|
@ -35,6 +43,10 @@ public:
|
|||
void registerIncubationController(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* findObjectGeneration(QObject* object);
|
||||
|
||||
|
@ -78,6 +90,7 @@ private:
|
|||
void postReload();
|
||||
void assignIncubationController();
|
||||
QVector<QPair<QQmlIncubationController*, QObject*>> incubationControllers;
|
||||
QHash<const void*, EngineGenerationExt*> extensions;
|
||||
|
||||
bool destroying = false;
|
||||
bool shouldTerminate = false;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
#include "ipc.hpp"
|
||||
#include <functional>
|
||||
#include <variant>
|
||||
|
||||
#include <qbuffer.h>
|
||||
#include <qlocalserver.h>
|
||||
#include <qlocalsocket.h>
|
||||
#include <qlogging.h>
|
||||
|
@ -8,6 +10,7 @@
|
|||
#include <qobject.h>
|
||||
|
||||
#include "generation.hpp"
|
||||
#include "ipccommand.hpp"
|
||||
#include "paths.hpp"
|
||||
|
||||
namespace qs::ipc {
|
||||
|
@ -62,20 +65,21 @@ void IpcServerConnection::onReadyRead() {
|
|||
this->stream.startTransaction();
|
||||
|
||||
this->stream.startTransaction();
|
||||
auto command = IpcCommand::Unknown;
|
||||
IpcCommand command;
|
||||
this->stream >> command;
|
||||
if (!this->stream.commitTransaction()) return;
|
||||
|
||||
switch (command) {
|
||||
case IpcCommand::Kill:
|
||||
qInfo() << "Exiting due to IPC request.";
|
||||
EngineGeneration::currentGeneration()->quit();
|
||||
break;
|
||||
default:
|
||||
qCCritical(logIpc) << "Received invalid IPC command from" << this;
|
||||
this->socket->disconnectFromServer();
|
||||
break;
|
||||
}
|
||||
std::visit(
|
||||
[this]<typename Command>(Command& command) {
|
||||
if constexpr (std::is_same_v<std::monostate, Command>) {
|
||||
qCCritical(logIpc) << "Received invalid IPC command from" << this;
|
||||
this->socket->disconnectFromServer();
|
||||
} else {
|
||||
command.exec(this);
|
||||
}
|
||||
},
|
||||
command
|
||||
);
|
||||
|
||||
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::waitForDisconnected() { this->socket.waitForDisconnected(); }
|
||||
|
||||
void IpcClient::kill() {
|
||||
qCDebug(logIpc) << "Sending kill command...";
|
||||
this->stream << IpcCommand::Kill;
|
||||
this->socket.flush();
|
||||
}
|
||||
void IpcClient::kill() { this->sendMessage(IpcCommand(IpcKillCommand())); }
|
||||
|
||||
void IpcClient::onError(QLocalSocket::LocalSocketError error) {
|
||||
qCCritical(logIpc) << "Socket Error" << error;
|
||||
|
@ -116,4 +116,10 @@ int IpcClient::connect(const QString& id, const std::function<void(IpcClient& cl
|
|||
callback(client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void IpcKillCommand::exec(IpcServerConnection* /*unused*/) {
|
||||
qInfo() << "Exiting due to IPC request.";
|
||||
EngineGeneration::currentGeneration()->quit();
|
||||
}
|
||||
|
||||
} // namespace qs::ipc
|
||||
|
|
168
src/core/ipc.hpp
168
src/core/ipc.hpp
|
@ -1,17 +1,133 @@
|
|||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include <qflags.h>
|
||||
#include <qlocalserver.h>
|
||||
#include <qlocalsocket.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.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 {
|
||||
|
||||
enum class IpcCommand : quint8 {
|
||||
Unknown = 0,
|
||||
Kill,
|
||||
Q_DECLARE_LOGGING_CATEGORY(logIpc);
|
||||
|
||||
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 {
|
||||
|
@ -35,13 +151,24 @@ class IpcServerConnection: public QObject {
|
|||
public:
|
||||
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:
|
||||
void onDisconnected();
|
||||
void onReadyRead();
|
||||
|
||||
private:
|
||||
QLocalSocket* socket;
|
||||
QDataStream stream;
|
||||
};
|
||||
|
||||
class IpcClient: public QObject {
|
||||
|
@ -56,19 +183,38 @@ public:
|
|||
|
||||
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
|
||||
connect(const QString& id, const std::function<void(IpcClient& client)>& callback);
|
||||
|
||||
// public for access by nonlocal handlers
|
||||
QLocalSocket socket;
|
||||
QDataStream stream;
|
||||
|
||||
signals:
|
||||
void connected();
|
||||
void disconnected();
|
||||
|
||||
private slots:
|
||||
static void onError(QLocalSocket::LocalSocketError error);
|
||||
|
||||
private:
|
||||
QLocalSocket socket;
|
||||
QDataStream stream;
|
||||
};
|
||||
|
||||
} // 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 <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <CLI/App.hpp>
|
||||
#include <CLI/CLI.hpp> // NOLINT: Need to include this for impls of some CLI11 classes
|
||||
|
@ -37,6 +38,7 @@
|
|||
#include <qtextstream.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../io/ipccomm.hpp"
|
||||
#include "build.hpp"
|
||||
#include "common.hpp"
|
||||
#include "instanceinfo.hpp"
|
||||
|
@ -157,10 +159,18 @@ struct CommandState {
|
|||
bool json = false;
|
||||
} output;
|
||||
|
||||
struct {
|
||||
bool info = false;
|
||||
QStringOption target;
|
||||
QStringOption function;
|
||||
std::vector<QStringOption> arguments;
|
||||
} ipc;
|
||||
|
||||
struct {
|
||||
CLI::App* log = nullptr;
|
||||
CLI::App* list = nullptr;
|
||||
CLI::App* kill = nullptr;
|
||||
CLI::App* msg = nullptr;
|
||||
} subcommand;
|
||||
|
||||
struct {
|
||||
|
@ -174,6 +184,7 @@ struct CommandState {
|
|||
int readLogFile(CommandState& cmd);
|
||||
int listInstances(CommandState& cmd);
|
||||
int killInstances(CommandState& cmd);
|
||||
int msgInstance(CommandState& cmd);
|
||||
int launchFromCommand(CommandState& cmd, QCoreApplication* coreApplication);
|
||||
|
||||
struct LaunchArgs {
|
||||
|
@ -268,6 +279,10 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
|||
};
|
||||
|
||||
auto cli = CLI::App();
|
||||
|
||||
// Require 0-1 subcommands. Without this, positionals can be parsed as more subcommands.
|
||||
cli.require_subcommand(0, 1);
|
||||
|
||||
addConfigSelection(&cli);
|
||||
addLoggingOptions(&cli, false);
|
||||
addDebugOptions(&cli);
|
||||
|
@ -331,6 +346,34 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
|||
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);
|
||||
|
||||
// Has to happen before extra threads are spawned.
|
||||
|
@ -389,6 +432,8 @@ int runCommand(int argc, char** argv, QCoreApplication* coreApplication) {
|
|||
return listInstances(state);
|
||||
} else if (*state.subcommand.kill) {
|
||||
return killInstances(state);
|
||||
} else if (*state.subcommand.msg) {
|
||||
return msgInstance(state);
|
||||
} else {
|
||||
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>
|
||||
QString base36Encode(T number) {
|
||||
const QString digits = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue