forked from quickshell/quickshell
service/greetd: add greetd service
This commit is contained in:
parent
72956185bd
commit
3573663ab6
11 changed files with 522 additions and 4 deletions
263
src/services/greetd/connection.cpp
Normal file
263
src/services/greetd/connection.cpp
Normal file
|
@ -0,0 +1,263 @@
|
|||
#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"
|
||||
|
||||
Q_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), // NOLINT
|
||||
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), // NOLINT
|
||||
sizeof(qint32)
|
||||
);
|
||||
|
||||
this->socket.write(text);
|
||||
this->socket.flush();
|
||||
}
|
||||
|
||||
GreetdConnection* GreetdConnection::instance() {
|
||||
static auto* instance = new GreetdConnection(); // NOLINT
|
||||
return instance;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue