diff --git a/changelog/next.md b/changelog/next.md index 4f550e8..ef63323 100644 --- a/changelog/next.md +++ b/changelog/next.md @@ -50,9 +50,7 @@ set shell id. - Fixed ClippingRectangle related crashes. - Fixed crashes when monitors are unplugged. - Fixed crashes when default pipewire devices are lost. -- Fixed ToplevelManager not clearing activeToplevel on deactivation. - Desktop action order is now preserved. -- Fixed partial socket reads in greetd and hyprland on slow machines. ## Packaging Changes diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index f0ca8ef..fb63f40 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -40,7 +40,6 @@ qt_add_library(quickshell-core STATIC scriptmodel.cpp colorquantizer.cpp toolsupport.cpp - streamreader.cpp ) qt_add_qml_module(quickshell-core diff --git a/src/core/streamreader.cpp b/src/core/streamreader.cpp deleted file mode 100644 index 1f66e29..0000000 --- a/src/core/streamreader.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "streamreader.hpp" -#include - -#include -#include -#include - -void StreamReader::setDevice(QIODevice* device) { - this->reset(); - this->device = device; -} - -void StreamReader::startTransaction() { - this->cursor = 0; - this->failed = false; -} - -bool StreamReader::fill() { - auto available = this->device->bytesAvailable(); - if (available <= 0) return false; - auto oldSize = this->buffer.size(); - this->buffer.resize(oldSize + available); - auto bytesRead = this->device->read(this->buffer.data() + oldSize, available); // NOLINT - - if (bytesRead <= 0) { - this->buffer.resize(oldSize); - return false; - } - - this->buffer.resize(oldSize + bytesRead); - return true; -} - -QByteArray StreamReader::readBytes(qsizetype count) { - if (this->failed) return {}; - - auto needed = this->cursor + count; - - while (this->buffer.size() < needed) { - if (!this->fill()) { - this->failed = true; - return {}; - } - } - - auto result = this->buffer.mid(this->cursor, count); - this->cursor += count; - return result; -} - -QByteArray StreamReader::readUntil(char terminator) { - if (this->failed) return {}; - - auto searchFrom = this->cursor; - auto idx = this->buffer.indexOf(terminator, searchFrom); - - while (idx == -1) { - searchFrom = this->buffer.size(); - if (!this->fill()) { - this->failed = true; - return {}; - } - - idx = this->buffer.indexOf(terminator, searchFrom); - } - - auto length = idx - this->cursor + 1; - auto result = this->buffer.mid(this->cursor, length); - this->cursor += length; - return result; -} - -void StreamReader::readInto(char* ptr, qsizetype count) { - auto data = this->readBytes(count); - if (!data.isEmpty()) memcpy(ptr, data.data(), count); -} - -qint32 StreamReader::readI32() { - qint32 value = 0; - this->readInto(reinterpret_cast(&value), sizeof(qint32)); - return value; -} - -bool StreamReader::commitTransaction() { - if (this->failed) { - this->cursor = 0; - return false; - } - - this->buffer.remove(0, this->cursor); - this->cursor = 0; - return true; -} - -void StreamReader::reset() { - this->buffer.clear(); - this->cursor = 0; -} diff --git a/src/core/streamreader.hpp b/src/core/streamreader.hpp deleted file mode 100644 index abf14ef..0000000 --- a/src/core/streamreader.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include -#include -#include - -class StreamReader { -public: - void setDevice(QIODevice* device); - - void startTransaction(); - QByteArray readBytes(qsizetype count); - QByteArray readUntil(char terminator); - void readInto(char* ptr, qsizetype count); - qint32 readI32(); - bool commitTransaction(); - void reset(); - -private: - bool fill(); - - QIODevice* device = nullptr; - QByteArray buffer; - qsizetype cursor = 0; - bool failed = false; -}; diff --git a/src/services/greetd/CMakeLists.txt b/src/services/greetd/CMakeLists.txt index a103531..2252f8c 100644 --- a/src/services/greetd/CMakeLists.txt +++ b/src/services/greetd/CMakeLists.txt @@ -12,7 +12,7 @@ qt_add_qml_module(quickshell-service-greetd install_qml_module(quickshell-service-greetd) # can't be Qt::Qml because generation.hpp pulls in gui types -target_link_libraries(quickshell-service-greetd PRIVATE Qt::Quick quickshell-core) +target_link_libraries(quickshell-service-greetd PRIVATE Qt::Quick) qs_module_pch(quickshell-service-greetd) diff --git a/src/services/greetd/connection.cpp b/src/services/greetd/connection.cpp index 3b8fa24..7130870 100644 --- a/src/services/greetd/connection.cpp +++ b/src/services/greetd/connection.cpp @@ -145,7 +145,6 @@ void GreetdConnection::setInactive() { QString GreetdConnection::user() const { return this->mUser; } void GreetdConnection::onSocketConnected() { - this->reader.setDevice(&this->socket); qCDebug(logGreetd) << "Connected to greetd socket."; if (this->mTargetActive) { @@ -161,84 +160,82 @@ void GreetdConnection::onSocketError(QLocalSocket::LocalSocketError error) { } void GreetdConnection::onSocketReady() { - while (true) { - this->reader.startTransaction(); - auto length = this->reader.readI32(); - auto text = this->reader.readBytes(length); - if (!this->reader.commitTransaction()) return; + qint32 length = 0; - auto json = QJsonDocument::fromJson(text).object(); - auto type = json.value("type").toString(); + this->socket.read(reinterpret_cast(&length), sizeof(qint32)); - qCDebug(logGreetd).noquote() << "Received greetd response:" << text; + auto text = this->socket.read(length); + auto json = QJsonDocument::fromJson(text).object(); + auto type = json.value("type").toString(); - if (type == "success") { - switch (this->mState) { - case GreetdState::Authenticating: - qCDebug(logGreetd) << "Authentication complete."; - this->mState = GreetdState::ReadyToLaunch; - emit this->stateChanged(); - emit this->readyToLaunch(); - break; - case GreetdState::Launching: - qCDebug(logGreetd) << "Target session set successfully."; - this->mState = GreetdState::Launched; - emit this->stateChanged(); - emit this->launched(); + qCDebug(logGreetd).noquote() << "Received greetd response:" << text; - if (this->mExitAfterLaunch) { - qCDebug(logGreetd) << "Quitting."; - EngineGeneration::currentGeneration()->quit(); - } + if (type == "success") { + switch (this->mState) { + case GreetdState::Authenticating: + qCDebug(logGreetd) << "Authentication complete."; + this->mState = GreetdState::ReadyToLaunch; + emit this->stateChanged(); + emit this->readyToLaunch(); + break; + case GreetdState::Launching: + qCDebug(logGreetd) << "Target session set successfully."; + this->mState = GreetdState::Launched; + emit this->stateChanged(); + emit this->launched(); - break; - default: goto unexpected; - } - } else if (type == "error") { - auto errorType = json.value("error_type").toString(); - auto desc = json.value("description").toString(); - - // Special case this error in case a session was already running. - // This cancels and restarts the session. - if (errorType == "error" && desc == "a session is already being configured") { - qCDebug( - logGreetd - ) << "A session was already in progress, cancelling it and starting a new one."; - this->setActive(false); - this->setActive(true); - return; + if (this->mExitAfterLaunch) { + qCDebug(logGreetd) << "Quitting."; + EngineGeneration::currentGeneration()->quit(); } - if (errorType == "auth_error") { - emit this->authFailure(desc); - this->setActive(false); - } else if (errorType == "error") { - qCWarning(logGreetd) << "Greetd error occurred" << desc; - emit this->error(desc); - } else goto unexpected; + break; + default: goto unexpected; + } + } else if (type == "error") { + auto errorType = json.value("error_type").toString(); + auto desc = json.value("description").toString(); - // errors terminate the session - this->setInactive(); - } else if (type == "auth_message") { - auto message = json.value("auth_message").toString(); - auto type = json.value("auth_message_type").toString(); - auto error = type == "error"; - auto responseRequired = type == "visible" || type == "secret"; - auto echoResponse = type != "secret"; + // Special case this error in case a session was already running. + // This cancels and restarts the session. + if (errorType == "error" && desc == "a session is already being configured") { + qCDebug( + logGreetd + ) << "A session was already in progress, cancelling it and starting a new one."; + this->setActive(false); + this->setActive(true); + return; + } - this->mResponseRequired = responseRequired; - emit this->authMessage(message, error, responseRequired, echoResponse); - - if (!responseRequired) { - this->sendRequest({{"type", "post_auth_message_response"}}); - } + if (errorType == "auth_error") { + emit this->authFailure(desc); + this->setActive(false); + } else if (errorType == "error") { + qCWarning(logGreetd) << "Greetd error occurred" << desc; + emit this->error(desc); } else goto unexpected; - continue; - unexpected: - qCCritical(logGreetd) << "Received unexpected greetd response" << text; - this->setActive(false); - } + // errors terminate the session + this->setInactive(); + } else if (type == "auth_message") { + auto message = json.value("auth_message").toString(); + auto type = json.value("auth_message_type").toString(); + auto error = type == "error"; + auto responseRequired = type == "visible" || type == "secret"; + auto echoResponse = type != "secret"; + + this->mResponseRequired = responseRequired; + emit this->authMessage(message, error, responseRequired, echoResponse); + + if (!responseRequired) { + this->sendRequest({{"type", "post_auth_message_response"}}); + } + } else goto unexpected; + + return; +unexpected: + qCCritical(logGreetd) << "Received unexpected greetd response" << text; + this->setActive(false); } void GreetdConnection::sendRequest(const QJsonObject& json) { diff --git a/src/services/greetd/connection.hpp b/src/services/greetd/connection.hpp index 89348dc..0c1d1eb 100644 --- a/src/services/greetd/connection.hpp +++ b/src/services/greetd/connection.hpp @@ -8,8 +8,6 @@ #include #include -#include "../../core/streamreader.hpp" - ///! State of the Greetd connection. /// See @@Greetd.state. class GreetdState: public QObject { @@ -76,5 +74,4 @@ private: bool mResponseRequired = false; QString mUser; QLocalSocket socket; - StreamReader reader; }; diff --git a/src/wayland/hyprland/ipc/CMakeLists.txt b/src/wayland/hyprland/ipc/CMakeLists.txt index 9e42520..fd01463 100644 --- a/src/wayland/hyprland/ipc/CMakeLists.txt +++ b/src/wayland/hyprland/ipc/CMakeLists.txt @@ -15,7 +15,7 @@ qs_add_module_deps_light(quickshell-hyprland-ipc Quickshell) install_qml_module(quickshell-hyprland-ipc) -target_link_libraries(quickshell-hyprland-ipc PRIVATE Qt::Quick quickshell-core) +target_link_libraries(quickshell-hyprland-ipc PRIVATE Qt::Quick) if (WAYLAND_TOPLEVEL_MANAGEMENT) target_sources(quickshell-hyprland-ipc PRIVATE diff --git a/src/wayland/hyprland/ipc/connection.cpp b/src/wayland/hyprland/ipc/connection.cpp index d2d5105..ad091a6 100644 --- a/src/wayland/hyprland/ipc/connection.cpp +++ b/src/wayland/hyprland/ipc/connection.cpp @@ -93,7 +93,6 @@ void HyprlandIpc::eventSocketError(QLocalSocket::LocalSocketError error) const { void HyprlandIpc::eventSocketStateChanged(QLocalSocket::LocalSocketState state) { if (state == QLocalSocket::ConnectedState) { - this->eventReader.setDevice(&this->eventSocket); qCInfo(logHyprlandIpc) << "Hyprland event socket connected."; emit this->connected(); } else if (state == QLocalSocket::UnconnectedState && this->valid) { @@ -105,11 +104,11 @@ void HyprlandIpc::eventSocketStateChanged(QLocalSocket::LocalSocketState state) void HyprlandIpc::eventSocketReady() { while (true) { - this->eventReader.startTransaction(); - auto rawEvent = this->eventReader.readUntil('\n'); - if (!this->eventReader.commitTransaction()) return; + auto rawEvent = this->eventSocket.readLine(); + if (rawEvent.isEmpty()) break; - rawEvent.chop(1); // remove trailing \n + // remove trailing \n + rawEvent.truncate(rawEvent.length() - 1); auto splitIdx = rawEvent.indexOf(">>"); auto event = QByteArrayView(rawEvent.data(), splitIdx); auto data = QByteArrayView( diff --git a/src/wayland/hyprland/ipc/connection.hpp b/src/wayland/hyprland/ipc/connection.hpp index ba1e7c9..e15d5cd 100644 --- a/src/wayland/hyprland/ipc/connection.hpp +++ b/src/wayland/hyprland/ipc/connection.hpp @@ -14,7 +14,6 @@ #include "../../../core/model.hpp" #include "../../../core/qmlscreen.hpp" -#include "../../../core/streamreader.hpp" #include "../../../wayland/toplevel_management/handle.hpp" namespace qs::hyprland::ipc { @@ -140,7 +139,6 @@ private: static bool compareWorkspaces(HyprlandWorkspace* a, HyprlandWorkspace* b); QLocalSocket eventSocket; - StreamReader eventReader; QString mRequestSocketPath; QString mEventSocketPath; bool valid = false; diff --git a/src/wayland/toplevel_management/qml.cpp b/src/wayland/toplevel_management/qml.cpp index 6a1d96b..0eae3de 100644 --- a/src/wayland/toplevel_management/qml.cpp +++ b/src/wayland/toplevel_management/qml.cpp @@ -161,11 +161,7 @@ void ToplevelManager::onToplevelReady(impl::ToplevelHandle* handle) { void ToplevelManager::onToplevelActiveChanged() { auto* toplevel = qobject_cast(this->sender()); - if (toplevel->activated()) { - this->setActiveToplevel(toplevel); - } else if (toplevel == this->mActiveToplevel) { - this->setActiveToplevel(nullptr); - } + if (toplevel->activated()) this->setActiveToplevel(toplevel); } void ToplevelManager::onToplevelClosed() { diff --git a/src/x11/i3/ipc/CMakeLists.txt b/src/x11/i3/ipc/CMakeLists.txt index a073459..c228ae3 100644 --- a/src/x11/i3/ipc/CMakeLists.txt +++ b/src/x11/i3/ipc/CMakeLists.txt @@ -17,7 +17,7 @@ qs_add_module_deps_light(quickshell-i3-ipc Quickshell) install_qml_module(quickshell-i3-ipc) -target_link_libraries(quickshell-i3-ipc PRIVATE Qt::Quick quickshell-core) +target_link_libraries(quickshell-i3-ipc PRIVATE Qt::Quick) qs_module_pch(quickshell-i3-ipc SET large) diff --git a/src/x11/i3/ipc/connection.cpp b/src/x11/i3/ipc/connection.cpp index 976167b..b765ebc 100644 --- a/src/x11/i3/ipc/connection.cpp +++ b/src/x11/i3/ipc/connection.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -14,7 +15,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -86,6 +89,9 @@ I3Ipc::I3Ipc(const QList& events): mEvents(events) { QObject::connect(&this->liveEventSocket, &QLocalSocket::readyRead, this, &I3Ipc::eventSocketReady); QObject::connect(&this->liveEventSocket, &QLocalSocket::connected, this, &I3Ipc::subscribe); // clang-format on + + this->liveEventSocketDs.setDevice(&this->liveEventSocket); + this->liveEventSocketDs.setByteOrder(static_cast(QSysInfo::ByteOrder)); } void I3Ipc::makeRequest(const QByteArray& request) { @@ -139,21 +145,34 @@ void I3Ipc::reconnectIPC() { } QVector I3Ipc::parseResponse() { - QVector events; + QVector> events; + const int magicLen = 6; - while (true) { - this->eventReader.startTransaction(); - auto magic = this->eventReader.readBytes(6); - auto size = this->eventReader.readI32(); - auto type = this->eventReader.readI32(); - auto payload = this->eventReader.readBytes(size); - if (!this->eventReader.commitTransaction()) return events; + while (!this->liveEventSocketDs.atEnd()) { + this->liveEventSocketDs.startTransaction(); + this->liveEventSocketDs.startTransaction(); - if (magic.size() < 6 || strncmp(magic.data(), MAGIC.data(), 6) != 0) { + std::array buffer = {}; + qint32 size = 0; + qint32 type = EventCode::Unknown; + + this->liveEventSocketDs.readRawData(buffer.data(), magicLen); + this->liveEventSocketDs >> size; + this->liveEventSocketDs >> type; + + if (!this->liveEventSocketDs.commitTransaction()) break; + + QByteArray payload(size, Qt::Uninitialized); + + this->liveEventSocketDs.readRawData(payload.data(), size); + + if (!this->liveEventSocketDs.commitTransaction()) break; + + if (strncmp(buffer.data(), MAGIC.data(), 6) != 0) { qCWarning(logI3Ipc) << "No magic sequence found in string."; this->reconnectIPC(); break; - } + }; if (I3IpcEvent::intToEvent(type) == EventCode::Unknown) { qCWarning(logI3Ipc) << "Received unknown event"; @@ -185,7 +204,6 @@ void I3Ipc::eventSocketError(QLocalSocket::LocalSocketError error) const { void I3Ipc::eventSocketStateChanged(QLocalSocket::LocalSocketState state) { if (state == QLocalSocket::ConnectedState) { - this->eventReader.setDevice(&this->liveEventSocket); qCInfo(logI3Ipc) << "I3 event socket connected."; emit this->connected(); } else if (state == QLocalSocket::UnconnectedState && this->valid) { diff --git a/src/x11/i3/ipc/connection.hpp b/src/x11/i3/ipc/connection.hpp index 7d03ecd..6100f7e 100644 --- a/src/x11/i3/ipc/connection.hpp +++ b/src/x11/i3/ipc/connection.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -8,8 +9,6 @@ #include #include -#include "../../../core/streamreader.hpp" - namespace qs::i3::ipc { constexpr std::string MAGIC = "i3-ipc"; @@ -93,7 +92,7 @@ protected: QVector> parseResponse(); QLocalSocket liveEventSocket; - StreamReader eventReader; + QDataStream liveEventSocketDs; QString mSocketPath; bool valid = false;