forked from quickshell/quickshell
feat(socket): add SocketServer and Socket.write
This commit is contained in:
parent
f004454395
commit
f45d298b66
|
@ -75,3 +75,13 @@ QObject* Reloadable::getChildByReloadId(QObject* parent, const QString& reloadId
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PostReloadHook::postReloadTree(QObject* root) {
|
||||||
|
for (auto* child: root->children()) {
|
||||||
|
PostReloadHook::postReloadTree(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto* self = dynamic_cast<PostReloadHook*>(root)) {
|
||||||
|
self->onPostReload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -111,3 +111,18 @@ private:
|
||||||
|
|
||||||
QList<QObject*> mChildren;
|
QList<QObject*> mChildren;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Hook that runs after the old widget tree is dropped during a reload.
|
||||||
|
class PostReloadHook {
|
||||||
|
public:
|
||||||
|
PostReloadHook() = default;
|
||||||
|
virtual ~PostReloadHook() = default;
|
||||||
|
PostReloadHook(PostReloadHook&&) = default;
|
||||||
|
PostReloadHook(const PostReloadHook&) = default;
|
||||||
|
PostReloadHook& operator=(PostReloadHook&&) = default;
|
||||||
|
PostReloadHook& operator=(const PostReloadHook&) = default;
|
||||||
|
|
||||||
|
virtual void onPostReload() = 0;
|
||||||
|
|
||||||
|
static void postReloadTree(QObject* root);
|
||||||
|
};
|
||||||
|
|
|
@ -7,8 +7,10 @@
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qqmlcomponent.h>
|
#include <qqmlcomponent.h>
|
||||||
#include <qqmlengine.h>
|
#include <qqmlengine.h>
|
||||||
|
#include <qtimer.h>
|
||||||
#include <qurl.h>
|
#include <qurl.h>
|
||||||
|
|
||||||
|
#include "reload.hpp"
|
||||||
#include "shell.hpp"
|
#include "shell.hpp"
|
||||||
#include "watcher.hpp"
|
#include "watcher.hpp"
|
||||||
|
|
||||||
|
@ -48,14 +50,21 @@ void RootWrapper::reloadGraph(bool hard) {
|
||||||
|
|
||||||
component.completeCreate();
|
component.completeCreate();
|
||||||
|
|
||||||
newRoot->onReload(hard ? nullptr : this->root);
|
auto* oldRoot = this->root;
|
||||||
|
this->root = newRoot;
|
||||||
|
|
||||||
if (this->root != nullptr) {
|
this->root->onReload(hard ? nullptr : oldRoot);
|
||||||
this->root->deleteLater();
|
|
||||||
this->root = nullptr;
|
if (oldRoot != nullptr) {
|
||||||
|
oldRoot->deleteLater();
|
||||||
|
|
||||||
|
QTimer::singleShot(0, [this, newRoot]() {
|
||||||
|
if (this->root == newRoot) PostReloadHook::postReloadTree(this->root);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
PostReloadHook::postReloadTree(newRoot);
|
||||||
}
|
}
|
||||||
|
|
||||||
this->root = newRoot;
|
|
||||||
this->onConfigChanged();
|
this->onConfigChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,24 @@
|
||||||
#include "socket.hpp"
|
#include "socket.hpp"
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include <qfile.h>
|
||||||
|
#include <qlocalserver.h>
|
||||||
#include <qlocalsocket.h>
|
#include <qlocalsocket.h>
|
||||||
|
#include <qlogging.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
|
#include <qqmlcomponent.h>
|
||||||
|
#include <qqmlengine.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
#include "datastream.hpp"
|
#include "datastream.hpp"
|
||||||
|
|
||||||
void Socket::setSocket(QLocalSocket* socket) {
|
void Socket::setSocket(QLocalSocket* socket) {
|
||||||
if (this->socket != nullptr) this->socket->deleteLater();
|
if (this->socket != nullptr) this->socket->deleteLater();
|
||||||
|
|
||||||
this->socket = socket;
|
this->socket = socket;
|
||||||
socket->setParent(this);
|
|
||||||
|
|
||||||
if (socket != nullptr) {
|
if (socket != nullptr) {
|
||||||
|
socket->setParent(this);
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
QObject::connect(this->socket, &QLocalSocket::connected, this, &Socket::onSocketConnected);
|
QObject::connect(this->socket, &QLocalSocket::connected, this, &Socket::onSocketConnected);
|
||||||
QObject::connect(this->socket, &QLocalSocket::disconnected, this, &Socket::onSocketDisconnected);
|
QObject::connect(this->socket, &QLocalSocket::disconnected, this, &Socket::onSocketDisconnected);
|
||||||
|
@ -75,3 +80,120 @@ void Socket::connectPathSocket() {
|
||||||
this->socket->connectToServer(QIODevice::ReadWrite);
|
this->socket->connectToServer(QIODevice::ReadWrite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Socket::write(const QString& data) {
|
||||||
|
if (this->socket != nullptr) {
|
||||||
|
this->socket->write(data.toUtf8());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketServer::~SocketServer() { this->disableServer(); }
|
||||||
|
|
||||||
|
void SocketServer::onPostReload() {
|
||||||
|
this->postReload = true;
|
||||||
|
if (this->isActivatable()) this->enableServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SocketServer::isActive() const { return this->server != nullptr; }
|
||||||
|
|
||||||
|
void SocketServer::setActive(bool active) {
|
||||||
|
this->activeTarget = active;
|
||||||
|
if (active == (this->server != nullptr)) return;
|
||||||
|
|
||||||
|
if (active) {
|
||||||
|
if (this->isActivatable()) this->enableServer();
|
||||||
|
} else this->disableServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SocketServer::path() const { return this->mPath; }
|
||||||
|
|
||||||
|
void SocketServer::setPath(QString path) {
|
||||||
|
if (this->mPath == path) return;
|
||||||
|
this->mPath = std::move(path);
|
||||||
|
emit this->pathChanged();
|
||||||
|
|
||||||
|
if (this->isActivatable()) this->enableServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
QQmlComponent* SocketServer::handler() const { return this->mHandler; }
|
||||||
|
|
||||||
|
void SocketServer::setHandler(QQmlComponent* handler) {
|
||||||
|
if (this->mHandler != nullptr) this->mHandler->deleteLater();
|
||||||
|
this->mHandler = handler;
|
||||||
|
|
||||||
|
if (handler != nullptr) {
|
||||||
|
handler->setParent(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SocketServer::isActivatable() {
|
||||||
|
return this->server == nullptr && this->postReload && this->activeTarget && !this->mPath.isEmpty()
|
||||||
|
&& this->handler() != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SocketServer::enableServer() {
|
||||||
|
this->disableServer();
|
||||||
|
|
||||||
|
this->server = new QLocalServer(this);
|
||||||
|
QObject::connect(
|
||||||
|
this->server,
|
||||||
|
&QLocalServer::newConnection,
|
||||||
|
this,
|
||||||
|
&SocketServer::onNewConnection
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!this->server->listen(this->mPath)) {
|
||||||
|
qWarning() << "could not start socket server at" << this->mPath;
|
||||||
|
this->disableServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->activeTarget = false;
|
||||||
|
emit this->activeStatusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SocketServer::disableServer() {
|
||||||
|
auto wasActive = this->server != nullptr;
|
||||||
|
|
||||||
|
if (this->server != nullptr) {
|
||||||
|
for (auto* socket: this->mSockets) {
|
||||||
|
socket->deleteLater();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->mSockets.clear();
|
||||||
|
this->server->deleteLater();
|
||||||
|
this->server = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->mPath != nullptr) {
|
||||||
|
if (QFile::exists(this->mPath) && !QFile::remove(this->mPath)) {
|
||||||
|
qWarning() << "failed to delete socket file at" << this->mPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wasActive) emit this->activeStatusChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SocketServer::onNewConnection() {
|
||||||
|
if (auto* connection = this->server->nextPendingConnection()) {
|
||||||
|
auto* instanceObj = this->mHandler->create(QQmlEngine::contextForObject(this));
|
||||||
|
auto* instance = qobject_cast<Socket*>(instanceObj);
|
||||||
|
|
||||||
|
if (instance == nullptr) {
|
||||||
|
qWarning() << "SocketServer.handler does not create a Socket. Dropping connection.";
|
||||||
|
if (instanceObj != nullptr) instanceObj->deleteLater();
|
||||||
|
connection->deleteLater();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->mSockets.append(instance);
|
||||||
|
instance->setParent(this);
|
||||||
|
|
||||||
|
if (instance->isConnected()) {
|
||||||
|
qWarning() << "SocketServer.handler created a socket with an existing connection. Dropping "
|
||||||
|
"new connection.";
|
||||||
|
connection->deleteLater();
|
||||||
|
} else {
|
||||||
|
instance->setSocket(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <qlocalserver.h>
|
||||||
#include <qlocalsocket.h>
|
#include <qlocalsocket.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
|
#include <qqmlcomponent.h>
|
||||||
|
#include <qqmlintegration.h>
|
||||||
|
#include <qtclasshelpermacros.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
#include "datastream.hpp"
|
#include "datastream.hpp"
|
||||||
|
#include "reload.hpp"
|
||||||
|
|
||||||
///! Unix socket listener.
|
///! Unix socket listener.
|
||||||
class Socket: public DataStream {
|
class Socket: public DataStream {
|
||||||
|
@ -24,6 +29,9 @@ class Socket: public DataStream {
|
||||||
public:
|
public:
|
||||||
explicit Socket(QObject* parent = nullptr): DataStream(parent) {}
|
explicit Socket(QObject* parent = nullptr): DataStream(parent) {}
|
||||||
|
|
||||||
|
/// Write data to the socket. Does nothing if not connected.
|
||||||
|
Q_INVOKABLE void write(const QString& data);
|
||||||
|
|
||||||
// takes ownership
|
// takes ownership
|
||||||
void setSocket(QLocalSocket* socket);
|
void setSocket(QLocalSocket* socket);
|
||||||
|
|
||||||
|
@ -56,3 +64,80 @@ private:
|
||||||
bool targetConnected = false;
|
bool targetConnected = false;
|
||||||
QString mPath;
|
QString mPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
///! Unix socket server.
|
||||||
|
/// #### Example
|
||||||
|
/// ```qml
|
||||||
|
/// SocketServer {
|
||||||
|
/// active: true
|
||||||
|
/// path: "/path/too/socket.sock"
|
||||||
|
/// handler: Socket {
|
||||||
|
/// onConnectedChanged: {
|
||||||
|
/// console.log(connected ? "new connection!" : "connection dropped!")
|
||||||
|
/// }
|
||||||
|
/// parser: SplitParser {
|
||||||
|
/// onRead: message => console.log(`read message from socket: ${message}`)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
class SocketServer
|
||||||
|
: public QObject
|
||||||
|
, public PostReloadHook {
|
||||||
|
Q_OBJECT;
|
||||||
|
/// If the socket server is currently active. Defaults to false.
|
||||||
|
///
|
||||||
|
/// Setting this to false will destory all active connections and delete
|
||||||
|
/// the socket file on disk.
|
||||||
|
///
|
||||||
|
/// If path is empty setting this property will have no effect.
|
||||||
|
Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeStatusChanged);
|
||||||
|
/// The path to create the socket server at.
|
||||||
|
///
|
||||||
|
/// Setting this property while the server is active will have no effect.
|
||||||
|
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged);
|
||||||
|
/// Connection handler component. Must creeate a `Socket`.
|
||||||
|
///
|
||||||
|
/// The created socket should not set `connected` or `path` or the incoming
|
||||||
|
/// socket connection will be dropped (they will be set by the socket server.)
|
||||||
|
/// Setting `connected` to false on the created socket after connection will
|
||||||
|
/// close and delete it.
|
||||||
|
Q_PROPERTY(QQmlComponent* handler READ handler WRITE setHandler NOTIFY handlerChanged);
|
||||||
|
QML_ELEMENT;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit SocketServer(QObject* parent = nullptr): QObject(parent) {}
|
||||||
|
~SocketServer() override;
|
||||||
|
Q_DISABLE_COPY_MOVE(SocketServer);
|
||||||
|
|
||||||
|
void onPostReload() override;
|
||||||
|
|
||||||
|
[[nodiscard]] bool isActive() const;
|
||||||
|
void setActive(bool active);
|
||||||
|
|
||||||
|
[[nodiscard]] QString path() const;
|
||||||
|
void setPath(QString path);
|
||||||
|
|
||||||
|
[[nodiscard]] QQmlComponent* handler() const;
|
||||||
|
void setHandler(QQmlComponent* handler);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void activeStatusChanged();
|
||||||
|
void pathChanged();
|
||||||
|
void handlerChanged();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onNewConnection();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool isActivatable();
|
||||||
|
void enableServer();
|
||||||
|
void disableServer();
|
||||||
|
|
||||||
|
QLocalServer* server = nullptr;
|
||||||
|
QQmlComponent* mHandler = nullptr;
|
||||||
|
QList<Socket*> mSockets;
|
||||||
|
bool activeTarget = false;
|
||||||
|
bool postReload = false;
|
||||||
|
QString mPath;
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue