forked from quickshell/quickshell
all: refactor windows code out of core
There are still some links from core to window but its now separate enough to fix PanelWindow in qml tooling.
This commit is contained in:
parent
1adad9e822
commit
4e48c6eefb
45 changed files with 1171 additions and 1142 deletions
7
src/ipc/CMakeLists.txt
Normal file
7
src/ipc/CMakeLists.txt
Normal file
|
@ -0,0 +1,7 @@
|
|||
qt_add_library(quickshell-ipc STATIC
|
||||
ipc.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(quickshell-ipc PRIVATE ${QT_DEPS})
|
||||
|
||||
target_link_libraries(quickshell PRIVATE quickshell-ipc)
|
125
src/ipc/ipc.cpp
Normal file
125
src/ipc/ipc.cpp
Normal file
|
@ -0,0 +1,125 @@
|
|||
#include "ipc.hpp"
|
||||
#include <functional>
|
||||
#include <variant>
|
||||
|
||||
#include <qbuffer.h>
|
||||
#include <qlocalserver.h>
|
||||
#include <qlocalsocket.h>
|
||||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.h>
|
||||
|
||||
#include "../core/generation.hpp"
|
||||
#include "../core/paths.hpp"
|
||||
#include "ipccommand.hpp"
|
||||
|
||||
namespace qs::ipc {
|
||||
|
||||
Q_LOGGING_CATEGORY(logIpc, "quickshell.ipc", QtWarningMsg);
|
||||
|
||||
IpcServer::IpcServer(const QString& path) {
|
||||
QObject::connect(&this->server, &QLocalServer::newConnection, this, &IpcServer::onNewConnection);
|
||||
|
||||
QLocalServer::removeServer(path);
|
||||
|
||||
if (!this->server.listen(path)) {
|
||||
qCCritical(logIpc) << "Failed to start IPC server on path" << path;
|
||||
return;
|
||||
}
|
||||
|
||||
qCInfo(logIpc) << "Started IPC server on path" << path;
|
||||
}
|
||||
|
||||
void IpcServer::start() {
|
||||
if (auto* run = QsPaths::instance()->instanceRunDir()) {
|
||||
auto path = run->filePath("ipc.sock");
|
||||
new IpcServer(path);
|
||||
} else {
|
||||
qCCritical(logIpc
|
||||
) << "Could not start IPC server as the instance runtime path could not be created.";
|
||||
}
|
||||
}
|
||||
|
||||
void IpcServer::onNewConnection() {
|
||||
while (auto* connection = this->server.nextPendingConnection()) {
|
||||
new IpcServerConnection(connection, this);
|
||||
}
|
||||
}
|
||||
|
||||
IpcServerConnection::IpcServerConnection(QLocalSocket* socket, IpcServer* server)
|
||||
: QObject(server)
|
||||
, socket(socket) {
|
||||
socket->setParent(this);
|
||||
this->stream.setDevice(socket);
|
||||
QObject::connect(socket, &QLocalSocket::disconnected, this, &IpcServerConnection::onDisconnected);
|
||||
QObject::connect(socket, &QLocalSocket::readyRead, this, &IpcServerConnection::onReadyRead);
|
||||
|
||||
qCInfo(logIpc) << "New IPC connection" << this;
|
||||
}
|
||||
|
||||
void IpcServerConnection::onDisconnected() {
|
||||
qCInfo(logIpc) << "IPC connection disconnected" << this;
|
||||
}
|
||||
|
||||
void IpcServerConnection::onReadyRead() {
|
||||
this->stream.startTransaction();
|
||||
|
||||
this->stream.startTransaction();
|
||||
IpcCommand command;
|
||||
this->stream >> command;
|
||||
if (!this->stream.commitTransaction()) return;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
IpcClient::IpcClient(const QString& path) {
|
||||
QObject::connect(&this->socket, &QLocalSocket::connected, this, &IpcClient::connected);
|
||||
QObject::connect(&this->socket, &QLocalSocket::disconnected, this, &IpcClient::disconnected);
|
||||
QObject::connect(&this->socket, &QLocalSocket::errorOccurred, this, &IpcClient::onError);
|
||||
|
||||
this->socket.connectToServer(path);
|
||||
this->stream.setDevice(&this->socket);
|
||||
}
|
||||
|
||||
bool IpcClient::isConnected() const { return this->socket.isValid(); }
|
||||
|
||||
void IpcClient::waitForConnected() { this->socket.waitForConnected(); }
|
||||
void IpcClient::waitForDisconnected() { this->socket.waitForDisconnected(); }
|
||||
|
||||
void IpcClient::kill() { this->sendMessage(IpcCommand(IpcKillCommand())); }
|
||||
|
||||
void IpcClient::onError(QLocalSocket::LocalSocketError error) {
|
||||
qCCritical(logIpc) << "Socket Error" << error;
|
||||
}
|
||||
|
||||
int IpcClient::connect(const QString& id, const std::function<void(IpcClient& client)>& callback) {
|
||||
auto path = QsPaths::ipcPath(id);
|
||||
auto client = IpcClient(path);
|
||||
qCDebug(logIpc) << "Connecting to instance" << id << "at" << path;
|
||||
|
||||
client.waitForConnected();
|
||||
if (!client.isConnected()) return -1;
|
||||
qCDebug(logIpc) << "Connected.";
|
||||
|
||||
callback(client);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void IpcKillCommand::exec(IpcServerConnection* /*unused*/) {
|
||||
qInfo() << "Exiting due to IPC request.";
|
||||
EngineGeneration::currentGeneration()->quit();
|
||||
}
|
||||
|
||||
} // namespace qs::ipc
|
220
src/ipc/ipc.hpp
Normal file
220
src/ipc/ipc.hpp
Normal file
|
@ -0,0 +1,220 @@
|
|||
#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 {
|
||||
|
||||
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 {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit IpcServer(const QString& path);
|
||||
|
||||
static void start();
|
||||
|
||||
private slots:
|
||||
void onNewConnection();
|
||||
|
||||
private:
|
||||
QLocalServer server;
|
||||
};
|
||||
|
||||
class IpcServerConnection: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
class IpcClient: public QObject {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
explicit IpcClient(const QString& path);
|
||||
|
||||
[[nodiscard]] bool isConnected() const;
|
||||
void waitForConnected();
|
||||
void waitForDisconnected();
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
} // namespace qs::ipc
|
20
src/ipc/ipccommand.hpp
Normal file
20
src/ipc/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
|
Loading…
Add table
Add a link
Reference in a new issue