forked from quickshell/quickshell
Fixes a bug in fb37be7
which ignored default logging categories due to
skipping QLoggingRegistry's filter.
260 lines
7 KiB
C++
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;
|
|
}
|