forked from quickshell/quickshell
		
	feat(socket): add unix socket listener
This commit is contained in:
		
							parent
							
								
									bb5bc0547a
								
							
						
					
					
						commit
						83a0ec6fc6
					
				
					 10 changed files with 479 additions and 0 deletions
				
			
		| 
						 | 
					@ -5,11 +5,16 @@ set(QT_MIN_VERSION "6.6.0")
 | 
				
			||||||
set(CMAKE_CXX_STANDARD 20)
 | 
					set(CMAKE_CXX_STANDARD 20)
 | 
				
			||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
 | 
					set(CMAKE_CXX_STANDARD_REQUIRED ON)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					option(TESTS "Build tests" OFF)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					option(SOCKETS "Enable unix socket support" ON)
 | 
				
			||||||
option(WAYLAND "Enable wayland support" ON)
 | 
					option(WAYLAND "Enable wayland support" ON)
 | 
				
			||||||
option(WAYLAND_WLR_LAYERSHELL "Support the zwlr_layer_shell_v1 wayland protocol" ON)
 | 
					option(WAYLAND_WLR_LAYERSHELL "Support the zwlr_layer_shell_v1 wayland protocol" ON)
 | 
				
			||||||
option(WAYLAND_SESSION_LOCK "Support the ext_session_lock_v1 wayland protocol" ON)
 | 
					option(WAYLAND_SESSION_LOCK "Support the ext_session_lock_v1 wayland protocol" ON)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message(STATUS "Quickshell configuration")
 | 
					message(STATUS "Quickshell configuration")
 | 
				
			||||||
 | 
					message(STATUS "  Build tests: ${TESTS}")
 | 
				
			||||||
 | 
					message(STATUS "  Sockets: ${SOCKETS}")
 | 
				
			||||||
message(STATUS "  Wayland: ${WAYLAND}")
 | 
					message(STATUS "  Wayland: ${WAYLAND}")
 | 
				
			||||||
if (WAYLAND)
 | 
					if (WAYLAND)
 | 
				
			||||||
	message(STATUS "    Wlroots Layershell: ${WAYLAND_WLR_LAYERSHELL}")
 | 
						message(STATUS "    Wlroots Layershell: ${WAYLAND_WLR_LAYERSHELL}")
 | 
				
			||||||
| 
						 | 
					@ -31,6 +36,16 @@ endif()
 | 
				
			||||||
set(QT_DEPS Qt6::Gui Qt6::Qml Qt6::Quick Qt6::QuickControls2)
 | 
					set(QT_DEPS Qt6::Gui Qt6::Qml Qt6::Quick Qt6::QuickControls2)
 | 
				
			||||||
set(QT_FPDEPS Gui Qml Quick QuickControls2)
 | 
					set(QT_FPDEPS Gui Qml Quick QuickControls2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (TESTS)
 | 
				
			||||||
 | 
						enable_testing()
 | 
				
			||||||
 | 
						list(APPEND QT_FPDEPS Test)
 | 
				
			||||||
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (SOCKETS)
 | 
				
			||||||
 | 
						list(APPEND QT_DEPS Qt6::Network)
 | 
				
			||||||
 | 
						list(APPEND QT_FPDEPS Network)
 | 
				
			||||||
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (WAYLAND)
 | 
					if (WAYLAND)
 | 
				
			||||||
	list(APPEND QT_DEPS Qt6::WaylandClient Qt6::WaylandClientPrivate)
 | 
						list(APPEND QT_DEPS Qt6::WaylandClient Qt6::WaylandClientPrivate)
 | 
				
			||||||
	list(APPEND QT_FPDEPS WaylandClient)
 | 
						list(APPEND QT_FPDEPS WaylandClient)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										3
									
								
								Justfile
									
										
									
									
									
								
							
							
						
						
									
										3
									
								
								Justfile
									
										
									
									
									
								
							| 
						 | 
					@ -25,3 +25,6 @@ clean:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
run *ARGS='': build
 | 
					run *ARGS='': build
 | 
				
			||||||
	{{builddir}}/src/core/quickshell {{ARGS}}
 | 
						{{builddir}}/src/core/quickshell {{ARGS}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test *ARGS='': build
 | 
				
			||||||
 | 
						ctest --test-dir build --output-on-failure {{ARGS}}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,8 +15,17 @@ qt_add_executable(quickshell
 | 
				
			||||||
	windowinterface.cpp
 | 
						windowinterface.cpp
 | 
				
			||||||
	floatingwindow.cpp
 | 
						floatingwindow.cpp
 | 
				
			||||||
	panelinterface.cpp
 | 
						panelinterface.cpp
 | 
				
			||||||
 | 
						datastream.cpp
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
qt_add_qml_module(quickshell URI Quickshell)
 | 
					qt_add_qml_module(quickshell URI Quickshell)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (SOCKETS)
 | 
				
			||||||
 | 
						target_sources(quickshell PRIVATE socket.cpp)
 | 
				
			||||||
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
target_link_libraries(quickshell PRIVATE ${QT_DEPS})
 | 
					target_link_libraries(quickshell PRIVATE ${QT_DEPS})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (TESTS)
 | 
				
			||||||
 | 
						add_subdirectory(test)
 | 
				
			||||||
 | 
					endif()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										108
									
								
								src/core/datastream.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								src/core/datastream.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,108 @@
 | 
				
			||||||
 | 
					#include "datastream.hpp"
 | 
				
			||||||
 | 
					#include <algorithm>
 | 
				
			||||||
 | 
					#include <utility>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qlocalsocket.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qtypes.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DataStreamParser* DataStream::reader() const { return this->mReader; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DataStream::setReader(DataStreamParser* reader) {
 | 
				
			||||||
 | 
						if (reader == this->mReader) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->mReader != nullptr) {
 | 
				
			||||||
 | 
							QObject::disconnect(this->mReader, nullptr, this, nullptr);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->mReader = reader;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (reader != nullptr) {
 | 
				
			||||||
 | 
							QObject::connect(reader, &QObject::destroyed, this, &DataStream::onReaderDestroyed);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						emit this->readerChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (reader != nullptr && !this->buffer.isEmpty()) {
 | 
				
			||||||
 | 
							reader->parseBytes(this->buffer, this->buffer);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DataStream::onReaderDestroyed() {
 | 
				
			||||||
 | 
						this->mReader = nullptr;
 | 
				
			||||||
 | 
						emit this->readerChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void DataStream::onBytesAvailable() {
 | 
				
			||||||
 | 
						auto buf = this->ioDevice()->readAll();
 | 
				
			||||||
 | 
						this->mReader->parseBytes(buf, this->buffer);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void SplitParser::parseBytes(QByteArray& incoming, QByteArray& buffer) {
 | 
				
			||||||
 | 
						if (this->mSplitMarker.isEmpty()) {
 | 
				
			||||||
 | 
							if (!buffer.isEmpty()) {
 | 
				
			||||||
 | 
								emit this->read(QString(buffer));
 | 
				
			||||||
 | 
								buffer.clear();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							emit this->read(QString(incoming));
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// make sure we dont miss any delimiters in the buffer if the delimiter changes
 | 
				
			||||||
 | 
						if (this->mSplitMarkerChanged) {
 | 
				
			||||||
 | 
							this->mSplitMarkerChanged = false;
 | 
				
			||||||
 | 
							this->parseBytes(buffer, buffer);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto marker = this->mSplitMarker.toUtf8();
 | 
				
			||||||
 | 
						auto mlen = marker.length();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						auto blen = buffer.size();
 | 
				
			||||||
 | 
						auto ilen = incoming.size();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						qsizetype start = &incoming == &buffer ? 0 : -blen;
 | 
				
			||||||
 | 
						for (auto readi = -std::min(blen, mlen - 1); readi <= ilen - mlen; readi++) {
 | 
				
			||||||
 | 
							for (auto marki = 0; marki < mlen; marki++) {
 | 
				
			||||||
 | 
								qint8 byte; // NOLINT
 | 
				
			||||||
 | 
								if (readi + marki < 0) byte = buffer[blen + readi + marki];
 | 
				
			||||||
 | 
								else byte = incoming[readi + marki];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (byte != marker[marki]) goto fail;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								QByteArray slice;
 | 
				
			||||||
 | 
								if (start < 0) slice = buffer.sliced(0, std::min(blen, blen + readi));
 | 
				
			||||||
 | 
								if (readi > 0) {
 | 
				
			||||||
 | 
									auto sstart = std::max(static_cast<qsizetype>(0), start);
 | 
				
			||||||
 | 
									slice.append(incoming.sliced(sstart, readi - sstart));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								readi += mlen;
 | 
				
			||||||
 | 
								start = readi;
 | 
				
			||||||
 | 
								emit this->read(QString(slice));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fail:;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (start < 0) {
 | 
				
			||||||
 | 
							buffer.append(incoming);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							// Will break the init case if inlined. Must be before clear.
 | 
				
			||||||
 | 
							auto slice = incoming.sliced(start);
 | 
				
			||||||
 | 
							buffer.clear();
 | 
				
			||||||
 | 
							buffer.insert(0, slice);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString SplitParser::splitMarker() const { return this->mSplitMarker; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void SplitParser::setSplitMarker(QString marker) {
 | 
				
			||||||
 | 
						if (marker == this->mSplitMarker) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->mSplitMarker = std::move(marker);
 | 
				
			||||||
 | 
						this->mSplitMarkerChanged = true;
 | 
				
			||||||
 | 
						emit this->splitMarkerChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										90
									
								
								src/core/datastream.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/core/datastream.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,90 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qbytearray.h>
 | 
				
			||||||
 | 
					#include <qlocalsocket.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlintegration.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qvariant.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DataStreamParser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					///! Data source that can be streamed into a parser.
 | 
				
			||||||
 | 
					/// See also: [DataStreamParser](../datastreamparser)
 | 
				
			||||||
 | 
					class DataStream: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						/// The parser to stream data from this source into.
 | 
				
			||||||
 | 
						/// If the parser is null no data will be read.
 | 
				
			||||||
 | 
						Q_PROPERTY(DataStreamParser* parser READ reader WRITE setReader NOTIFY readerChanged);
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
						QML_UNCREATABLE("base class");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit DataStream(QObject* parent = nullptr): QObject(parent) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] DataStreamParser* reader() const;
 | 
				
			||||||
 | 
						void setReader(DataStreamParser* reader);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						void readerChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public slots:
 | 
				
			||||||
 | 
						void onBytesAvailable();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					protected:
 | 
				
			||||||
 | 
						[[nodiscard]] virtual QIODevice* ioDevice() const = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private slots:
 | 
				
			||||||
 | 
						void onReaderDestroyed();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						DataStreamParser* mReader = nullptr;
 | 
				
			||||||
 | 
						QByteArray buffer;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					///! Parser for streamed input data.
 | 
				
			||||||
 | 
					/// See also: [DataStream](../datastream), [SplitParser](../splitparser)
 | 
				
			||||||
 | 
					class DataStreamParser: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
						QML_UNCREATABLE("base class");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit DataStreamParser(QObject* parent = nullptr): QObject(parent) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// the buffer will be sent in both slots if there is data remaining from a previous parser
 | 
				
			||||||
 | 
						virtual void parseBytes(QByteArray& incoming, QByteArray& buffer) = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						/// Emitted when data is read from the stream.
 | 
				
			||||||
 | 
						void read(QString data);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					///! Parser for delimited data streams.
 | 
				
			||||||
 | 
					/// Parser for delimited data streams. [read()] is emitted once per delimited chunk of the stream.
 | 
				
			||||||
 | 
					///
 | 
				
			||||||
 | 
					/// [read()]: ../datastreamparser#sig.read
 | 
				
			||||||
 | 
					class SplitParser: public DataStreamParser {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						/// The delimiter for parsed data. May be multiple characters. Defaults to `\n`.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// If the delimiter is empty read lengths may be arbitrary (whatever is returned by the
 | 
				
			||||||
 | 
						/// underlying read call.)
 | 
				
			||||||
 | 
						Q_PROPERTY(QString splitMarker READ splitMarker WRITE setSplitMarker NOTIFY splitMarkerChanged);
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit SplitParser(QObject* parent = nullptr): DataStreamParser(parent) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void parseBytes(QByteArray& incoming, QByteArray& buffer) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QString splitMarker() const;
 | 
				
			||||||
 | 
						void setSplitMarker(QString marker);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						void splitMarkerChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						QString mSplitMarker = "\n";
 | 
				
			||||||
 | 
						bool mSplitMarkerChanged = false;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -12,5 +12,7 @@ headers = [
 | 
				
			||||||
	"windowinterface.hpp",
 | 
						"windowinterface.hpp",
 | 
				
			||||||
	"panelinterface.hpp",
 | 
						"panelinterface.hpp",
 | 
				
			||||||
	"floatingwindow.hpp",
 | 
						"floatingwindow.hpp",
 | 
				
			||||||
 | 
						"datastream.hpp",
 | 
				
			||||||
 | 
						"socket.hpp",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
-----
 | 
					-----
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										77
									
								
								src/core/socket.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/core/socket.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,77 @@
 | 
				
			||||||
 | 
					#include "socket.hpp"
 | 
				
			||||||
 | 
					#include <utility>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qlocalsocket.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "datastream.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Socket::setSocket(QLocalSocket* socket) {
 | 
				
			||||||
 | 
						if (this->socket != nullptr) this->socket->deleteLater();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						this->socket = socket;
 | 
				
			||||||
 | 
						socket->setParent(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (socket != nullptr) {
 | 
				
			||||||
 | 
							// clang-format off
 | 
				
			||||||
 | 
							QObject::connect(this->socket, &QLocalSocket::connected, this, &Socket::onSocketConnected);
 | 
				
			||||||
 | 
							QObject::connect(this->socket, &QLocalSocket::disconnected, this, &Socket::onSocketDisconnected);
 | 
				
			||||||
 | 
							QObject::connect(this->socket, &QLocalSocket::errorOccurred, this, &Socket::error);
 | 
				
			||||||
 | 
							QObject::connect(this->socket, &QLocalSocket::readyRead, this, &DataStream::onBytesAvailable);
 | 
				
			||||||
 | 
							// clang-format on
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (socket->isOpen()) this->onSocketConnected();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString Socket::path() const { return this->mPath; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Socket::setPath(QString path) {
 | 
				
			||||||
 | 
						if ((this->connected && !this->disconnecting) || path == this->mPath) return;
 | 
				
			||||||
 | 
						this->mPath = std::move(path);
 | 
				
			||||||
 | 
						emit this->pathChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->targetConnected && this->socket == nullptr) this->connectPathSocket();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Socket::onSocketConnected() {
 | 
				
			||||||
 | 
						this->connected = true;
 | 
				
			||||||
 | 
						this->targetConnected = false;
 | 
				
			||||||
 | 
						this->disconnecting = false;
 | 
				
			||||||
 | 
						emit this->connectionStateChanged();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Socket::onSocketDisconnected() {
 | 
				
			||||||
 | 
						this->connected = false;
 | 
				
			||||||
 | 
						this->disconnecting = false;
 | 
				
			||||||
 | 
						this->socket->deleteLater();
 | 
				
			||||||
 | 
						this->socket = nullptr;
 | 
				
			||||||
 | 
						emit this->connectionStateChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (this->targetConnected) this->connectPathSocket();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool Socket::isConnected() const { return this->connected; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Socket::setConnected(bool connected) {
 | 
				
			||||||
 | 
						this->targetConnected = connected;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!connected) {
 | 
				
			||||||
 | 
							if (this->socket != nullptr && !this->disconnecting) {
 | 
				
			||||||
 | 
								this->disconnecting = true;
 | 
				
			||||||
 | 
								this->socket->disconnectFromServer();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else if (this->socket == nullptr) this->connectPathSocket();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QIODevice* Socket::ioDevice() const { return this->socket; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Socket::connectPathSocket() {
 | 
				
			||||||
 | 
						if (!this->mPath.isEmpty()) {
 | 
				
			||||||
 | 
							auto* socket = new QLocalSocket();
 | 
				
			||||||
 | 
							socket->setServerName(this->mPath);
 | 
				
			||||||
 | 
							this->setSocket(socket);
 | 
				
			||||||
 | 
							this->socket->connectToServer(QIODevice::ReadWrite);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										58
									
								
								src/core/socket.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/core/socket.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,58 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qlocalsocket.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "datastream.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) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										7
									
								
								src/core/test/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/core/test/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					function (qs_test name)
 | 
				
			||||||
 | 
						add_executable(${name} ${ARGN})
 | 
				
			||||||
 | 
						target_link_libraries(${name} PRIVATE ${QT_DEPS} Qt6::Test)
 | 
				
			||||||
 | 
						add_test(NAME ${name} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" COMMAND $<TARGET_FILE:${name}>)
 | 
				
			||||||
 | 
					endfunction()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qs_test(datastream datastream.cpp ../datastream.cpp)
 | 
				
			||||||
							
								
								
									
										110
									
								
								src/core/test/datastream.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								src/core/test/datastream.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,110 @@
 | 
				
			||||||
 | 
					#include "../datastream.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qbytearray.h>
 | 
				
			||||||
 | 
					#include <qlist.h>
 | 
				
			||||||
 | 
					#include <qlogging.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qsignalspy.h>
 | 
				
			||||||
 | 
					#include <qtest.h>
 | 
				
			||||||
 | 
					#include <qtestcase.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TestSplitParser: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
					private slots:
 | 
				
			||||||
 | 
						void splits_data() { // NOLINT
 | 
				
			||||||
 | 
							QTest::addColumn<QString>("mark");
 | 
				
			||||||
 | 
							QTest::addColumn<QString>("buffer");   // max that can go in the buffer
 | 
				
			||||||
 | 
							QTest::addColumn<QString>("incoming"); // data that has to be tested on the end in one go
 | 
				
			||||||
 | 
							QTest::addColumn<QList<QString>>("results");
 | 
				
			||||||
 | 
							QTest::addColumn<QString>("remainder");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// NOLINTBEGIN
 | 
				
			||||||
 | 
							// clang-format off
 | 
				
			||||||
 | 
							QTest::addRow("simple") << "-"
 | 
				
			||||||
 | 
								<< "foo" << "-"
 | 
				
			||||||
 | 
								<< QList<QString>("foo") << "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							QTest::addRow("multiple") << "-"
 | 
				
			||||||
 | 
								<< "foo" << "-bar-baz-"
 | 
				
			||||||
 | 
								<< QList<QString>({ "foo", "bar", "baz" }) << "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							QTest::addRow("incomplete") << "-"
 | 
				
			||||||
 | 
								<< "foo" << "-bar-baz"
 | 
				
			||||||
 | 
								<< QList<QString>({ "foo", "bar" }) << "baz";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							QTest::addRow("longsplit") << "12345"
 | 
				
			||||||
 | 
								<< "foo1234" << "5bar12345"
 | 
				
			||||||
 | 
								<< QList<QString>({ "foo", "bar" }) << "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							QTest::addRow("longsplit-incomplete") << "123"
 | 
				
			||||||
 | 
								<< "foo12" << "3bar123baz"
 | 
				
			||||||
 | 
								<< QList<QString>({ "foo", "bar" }) << "baz";
 | 
				
			||||||
 | 
							// clang-format on
 | 
				
			||||||
 | 
							// NOLINTEND
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void splits() { // NOLINT
 | 
				
			||||||
 | 
							// NOLINTBEGIN
 | 
				
			||||||
 | 
							QFETCH(QString, mark);
 | 
				
			||||||
 | 
							QFETCH(QString, buffer);
 | 
				
			||||||
 | 
							QFETCH(QString, incoming);
 | 
				
			||||||
 | 
							QFETCH(QList<QString>, results);
 | 
				
			||||||
 | 
							QFETCH(QString, remainder);
 | 
				
			||||||
 | 
							// NOLINTEND
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto bufferArray = buffer.toUtf8();
 | 
				
			||||||
 | 
							auto incomingArray = incoming.toUtf8();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (auto i = 0; i <= bufferArray.length(); i++) {
 | 
				
			||||||
 | 
								auto buffer = bufferArray.sliced(0, i);
 | 
				
			||||||
 | 
								auto incoming = bufferArray.sliced(i);
 | 
				
			||||||
 | 
								incoming.append(incomingArray);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								qInfo() << "BUFFER" << QString(buffer);
 | 
				
			||||||
 | 
								qInfo() << "INCOMING" << QString(incoming);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								auto parser = SplitParser();
 | 
				
			||||||
 | 
								auto spy = QSignalSpy(&parser, &DataStreamParser::read);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								parser.setSplitMarker(mark);
 | 
				
			||||||
 | 
								parser.parseBytes(incoming, buffer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								auto actualResults = QList<QString>();
 | 
				
			||||||
 | 
								for (auto& read: spy) {
 | 
				
			||||||
 | 
									actualResults.push_back(read[0].toString());
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								qInfo() << "EXPECTED RESULTS" << results;
 | 
				
			||||||
 | 
								qInfo() << "ACTUAL RESULTS" << actualResults;
 | 
				
			||||||
 | 
								qInfo() << "EXPECTED REMAINDER" << remainder;
 | 
				
			||||||
 | 
								qInfo() << "ACTUAL REMAINDER" << remainder;
 | 
				
			||||||
 | 
								QCOMPARE(actualResults, results);
 | 
				
			||||||
 | 
								QCOMPARE(buffer, remainder);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void initBuffer() { // NOLINT
 | 
				
			||||||
 | 
							auto parser = SplitParser();
 | 
				
			||||||
 | 
							auto spy = QSignalSpy(&parser, &DataStreamParser::read);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto buf = QString("foo-bar-baz").toUtf8();
 | 
				
			||||||
 | 
							auto expected = QList<QString>({"foo", "bar"});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							parser.setSplitMarker("-");
 | 
				
			||||||
 | 
							parser.parseBytes(buf, buf);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							auto actualResults = QList<QString>();
 | 
				
			||||||
 | 
							for (auto& read: spy) {
 | 
				
			||||||
 | 
								actualResults.push_back(read[0].toString());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							qInfo() << "EXPECTED RESULTS" << expected;
 | 
				
			||||||
 | 
							qInfo() << "ACTUAL RESULTS" << actualResults;
 | 
				
			||||||
 | 
							QCOMPARE(actualResults, expected);
 | 
				
			||||||
 | 
							QCOMPARE(buf, "baz");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QTEST_MAIN(TestSplitParser)
 | 
				
			||||||
 | 
					#include "datastream.moc"
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue