quickshell/src/services/greetd/connection.cpp
outfoxxed 3d594e16dd
core/log: track default logging categories
Fixes a bug in fb37be7 which ignored default logging categories due to
skipping QLoggingRegistry's filter.
2025-07-08 13:49:06 -07:00

260 lines
7 KiB
C++

#include "connection.hpp"
#include <utility>
#include <qcontainerfwd.h>
#include <qjsonarray.h>
#include <qjsondocument.h>
#include <qjsonobject.h>
#include <qlocalsocket.h>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qobject.h>
#include <qtenvironmentvariables.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "../../core/generation.hpp"
#include "../../core/logcat.hpp"
namespace {
QS_LOGGING_CATEGORY(logGreetd, "quickshell.service.greetd");
}
QString GreetdState::toString(GreetdState::Enum value) {
switch (value) {
case GreetdState::Inactive: return "Inactive";
case GreetdState::Authenticating: return "Authenticating";
case GreetdState::ReadyToLaunch: return "Ready to Launch";
case GreetdState::Launching: return "Launching";
case GreetdState::Launched: return "Launched";
default: return "Invalid";
}
}
GreetdConnection::GreetdConnection() {
auto socket = qEnvironmentVariable("GREETD_SOCK");
if (socket.isEmpty()) {
this->mAvailable = false;
return;
}
this->mAvailable = true;
// clang-format off
QObject::connect(&this->socket, &QLocalSocket::connected, this, &GreetdConnection::onSocketConnected);
QObject::connect(&this->socket, &QLocalSocket::readyRead, this, &GreetdConnection::onSocketReady);
// clang-format on
this->socket.connectToServer(socket, QLocalSocket::ReadWrite);
}
void GreetdConnection::createSession(QString user) {
if (!this->mAvailable) {
qCCritical(logGreetd) << "Greetd is not available.";
return;
}
if (user != this->mUser) {
this->mUser = std::move(user);
emit this->userChanged();
}
this->setActive(true);
}
void GreetdConnection::cancelSession() { this->setActive(false); }
void GreetdConnection::respond(QString response) {
if (!this->mResponseRequired) {
qCCritical(logGreetd) << "Cannot respond to greetd as a response is not currently required.";
return;
}
this->sendRequest({
{"type", "post_auth_message_response"},
{"response", response},
});
this->mResponseRequired = false;
}
void GreetdConnection::launch(
const QList<QString>& command,
const QList<QString>& environment,
bool exit
) {
if (this->mState != GreetdState::ReadyToLaunch) {
qCCritical(logGreetd) << "Cannot call launch() as state is not currently ReadyToLaunch.";
return;
}
this->mState = GreetdState::Launching;
this->mExitAfterLaunch = exit;
this->sendRequest({
{"type", "start_session"},
{"cmd", QJsonArray::fromStringList(command)},
{"env", QJsonArray::fromStringList(environment)},
});
}
bool GreetdConnection::isAvailable() const { return this->mAvailable; }
GreetdState::Enum GreetdConnection::state() const { return this->mState; }
void GreetdConnection::setActive(bool active) {
if (this->socket.state() == QLocalSocket::ConnectedState) {
this->mTargetActive = active;
if (active == (this->mState != GreetdState::Inactive)) return;
if (active) {
if (this->mUser.isEmpty()) {
qCCritical(logGreetd) << "Cannot activate greetd with unset user.";
this->setActive(false);
return;
}
this->sendRequest({
{"type", "create_session"},
{"username", this->mUser},
});
this->mState = GreetdState::Authenticating;
emit this->stateChanged();
} else {
this->sendRequest({
{"type", "cancel_session"},
});
this->setInactive();
}
} else {
if (active != this->mTargetActive) {
this->mTargetActive = active;
}
}
}
void GreetdConnection::setInactive() {
this->mTargetActive = false;
this->mResponseRequired = false;
this->mState = GreetdState::Inactive;
emit this->stateChanged();
}
QString GreetdConnection::user() const { return this->mUser; }
void GreetdConnection::onSocketConnected() {
qCDebug(logGreetd) << "Connected to greetd socket.";
if (this->mTargetActive) {
this->setActive(true);
}
}
void GreetdConnection::onSocketError(QLocalSocket::LocalSocketError error) {
qCCritical(logGreetd) << "Greetd socket encountered an error and cannot continue:" << error;
this->mAvailable = false;
this->setActive(false);
}
void GreetdConnection::onSocketReady() {
qint32 length = 0;
this->socket.read(reinterpret_cast<char*>(&length), sizeof(qint32));
auto text = this->socket.read(length);
auto json = QJsonDocument::fromJson(text).object();
auto type = json.value("type").toString();
qCDebug(logGreetd).noquote() << "Received greetd response:" << text;
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();
if (this->mExitAfterLaunch) {
qCDebug(logGreetd) << "Quitting.";
EngineGeneration::currentGeneration()->quit();
}
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 (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;
// 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);
} else goto unexpected;
return;
unexpected:
qCCritical(logGreetd) << "Received unexpected greetd response" << text;
this->setActive(false);
}
void GreetdConnection::sendRequest(const QJsonObject& json) {
auto text = QJsonDocument(json).toJson(QJsonDocument::Compact);
auto length = static_cast<qint32>(text.length());
if (logGreetd().isDebugEnabled()) {
auto debugJson = json;
if (json.value("type").toString() == "post_auth_message_response") {
debugJson["response"] = "<CENSORED>";
}
qCDebug(logGreetd).noquote() << "Sending greetd request:"
<< QJsonDocument(debugJson).toJson(QJsonDocument::Compact);
}
this->socket.write(reinterpret_cast<char*>(&length), sizeof(qint32));
this->socket.write(text);
this->socket.flush();
}
GreetdConnection* GreetdConnection::instance() {
static auto* instance = new GreetdConnection(); // NOLINT
return instance;
}