quickshell/src/core/socket.hpp

144 lines
4 KiB
C++

#pragma once
#include <qlocalserver.h>
#include <qlocalsocket.h>
#include <qobject.h>
#include <qqmlcomponent.h>
#include <qqmlintegration.h>
#include <qtclasshelpermacros.h>
#include <qtmetamacros.h>
#include "datastream.hpp"
#include "reload.hpp"
///! Unix socket listener.
class Socket: public DataStream {
Q_OBJECT;
/// Returns if the socket is currently connected.
///
/// Writing to this property will set the target connection state and will not
/// update the property immediately. Setting the property to false will begin disconnecting
/// the socket, and setting it to true will begin connecting the socket if path is not empty.
Q_PROPERTY(bool connected READ isConnected WRITE setConnected NOTIFY connectionStateChanged);
/// The path to connect this socket to when `connected` is set to true.
///
/// Changing this property will have no effect while the connection is active.
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged);
QML_ELEMENT;
public:
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
void setSocket(QLocalSocket* socket);
[[nodiscard]] bool isConnected() const;
void setConnected(bool connected);
[[nodiscard]] QString path() const;
void setPath(QString path);
signals:
/// This signal is sent whenever a socket error is encountered.
void error(QLocalSocket::LocalSocketError error);
void connectionStateChanged();
void pathChanged();
protected:
[[nodiscard]] QIODevice* ioDevice() const override;
private slots:
void onSocketConnected();
void onSocketDisconnected();
private:
void connectPathSocket();
QLocalSocket* socket = nullptr;
bool connected = false;
bool disconnecting = false;
bool targetConnected = false;
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;
};