This commit is contained in:
Maximilian Seidler 2025-05-09 18:03:01 +01:00 committed by GitHub
commit 95a0ed15a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 1105 additions and 44 deletions

View file

@ -72,6 +72,7 @@ find_package(Threads REQUIRED)
find_package(PkgConfig REQUIRED)
find_package(OpenGL REQUIRED COMPONENTS EGL GLES3)
find_package(hyprwayland-scanner 0.4.4 REQUIRED)
find_package(glaze REQUIRED)
pkg_check_modules(
deps
REQUIRED
@ -93,7 +94,7 @@ pkg_check_modules(
file(GLOB_RECURSE SRCFILES CONFIGURE_DEPENDS "src/*.cpp")
add_executable(hyprlock ${SRCFILES})
target_link_libraries(hyprlock PRIVATE pam rt Threads::Threads PkgConfig::deps
OpenGL::EGL OpenGL::GLES3)
OpenGL::EGL OpenGL::GLES3 glaze::glaze)
# protocols
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
@ -128,7 +129,7 @@ function(protocolWayland)
endfunction()
make_directory(${CMAKE_SOURCE_DIR}/protocols) # we don't ship any custom ones so
# the dir won't be there
# the dir won't be there
protocolwayland()

View file

@ -13,11 +13,11 @@
]
},
"locked": {
"lastModified": 1743953322,
"narHash": "sha256-prQ5JKopXtzCMX2eT3dXbaVvGmzjMRE2bXStQDdazpM=",
"lastModified": 1745015490,
"narHash": "sha256-apEJ9zoSzmslhJ2vOKFcXTMZLUFYzh1ghfB6Rbw3Low=",
"owner": "hyprwm",
"repo": "hyprgraphics",
"rev": "9d7f2687c84c729afbc3b13f7937655570f2978d",
"rev": "60754910946b4e2dc1377b967b7156cb989c5873",
"type": "github"
},
"original": {
@ -39,11 +39,11 @@
]
},
"locked": {
"lastModified": 1744468525,
"narHash": "sha256-9HySx+EtsbbKlZDlY+naqqOV679VdxP6x6fP3wxDXJk=",
"lastModified": 1745357019,
"narHash": "sha256-q/C3qj9FWHQenObXuw/nGIT8iIsWFjgmcQYcA+ZfpPs=",
"owner": "hyprwm",
"repo": "hyprlang",
"rev": "f1000c54d266e6e4e9d646df0774fac5b8a652df",
"rev": "397600c42b8d7a443a5b4e92aa15f46650a90f18",
"type": "github"
},
"original": {
@ -62,11 +62,11 @@
]
},
"locked": {
"lastModified": 1743950287,
"narHash": "sha256-/6IAEWyb8gC/NKZElxiHChkouiUOrVYNq9YqG0Pzm4Y=",
"lastModified": 1745975815,
"narHash": "sha256-s3GzsRxBL/N/xYgUXZhQh4t62uR1BN4zxXgWBtJ3lWM=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "f2dc70e448b994cef627a157ee340135bd68fbc6",
"rev": "05878d9470c9e5cbc8807813f9ec2006627a0ca0",
"type": "github"
},
"original": {
@ -100,11 +100,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1744463964,
"narHash": "sha256-LWqduOgLHCFxiTNYi3Uj5Lgz0SR+Xhw3kr/3Xd0GPTM=",
"lastModified": 1746461020,
"narHash": "sha256-7+pG1I9jvxNlmln4YgnlW4o+w0TZX24k688mibiFDUE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2631b0b7abcea6e640ce31cd78ea58910d31e650",
"rev": "3730d8a308f94996a9ba7c7138ede69c1b9ac4ae",
"type": "github"
},
"original": {

View file

@ -2,8 +2,9 @@
lib,
stdenv,
cmake,
pkg-config,
cairo,
glaze,
pkg-config,
libdrm,
libGL,
libxkbcommon,
@ -37,6 +38,7 @@ stdenv.mkDerivation {
buildInputs = [
cairo
glaze
libdrm
libGL
libxkbcommon

View file

@ -1,20 +1,26 @@
#include "Auth.hpp"
#include "Pam.hpp"
#include "Fingerprint.hpp"
#include "../config/ConfigManager.hpp"
#include "../core/hyprlock.hpp"
#include "Fingerprint.hpp"
#include "GreetdLogin.hpp"
#include "Pam.hpp"
#include "src/helpers/Log.hpp"
#include <hyprlang.hpp>
#include <memory>
CAuth::CAuth() {
static const auto ENABLEPAM = g_pConfigManager->getValue<Hyprlang::INT>("auth:pam:enabled");
if (*ENABLEPAM)
m_vImpls.emplace_back(makeShared<CPam>());
CAuth::CAuth(bool sessionLogin) {
static const auto ENABLEPAM = g_pConfigManager->getValue<Hyprlang::INT>("auth:pam:enabled");
static const auto ENABLEFINGERPRINT = g_pConfigManager->getValue<Hyprlang::INT>("auth:fingerprint:enabled");
if (*ENABLEFINGERPRINT)
m_vImpls.emplace_back(makeShared<CFingerprint>());
if (sessionLogin)
m_vImpls.emplace_back(makeShared<CGreetdLogin>());
else {
if (*ENABLEPAM)
m_vImpls.emplace_back(makeShared<CPam>());
if (*ENABLEFINGERPRINT)
m_vImpls.emplace_back(makeShared<CFingerprint>());
}
RASSERT(!m_vImpls.empty(), "At least one authentication method must be enabled!");
}

View file

@ -9,6 +9,7 @@
enum eAuthImplementations {
AUTH_IMPL_PAM = 0,
AUTH_IMPL_FINGERPRINT = 1,
AUTH_IMPL_GREETD = 2,
};
class IAuthImplementation {
@ -28,7 +29,7 @@ class IAuthImplementation {
class CAuth {
public:
CAuth();
CAuth(bool sessionLogin);
void start();

357
src/auth/GreetdLogin.cpp Normal file
View file

@ -0,0 +1,357 @@
#include "GreetdLogin.hpp"
#include "../config/ConfigManager.hpp"
#include "../config/LoginSessionManager.hpp"
#include "../core/hyprlock.hpp"
#include "../helpers/Log.hpp"
#include "../helpers/MiscFunctions.hpp"
#include <hyprutils/string/VarList.hpp>
#include <hyprutils/os/Process.hpp>
#include <sys/socket.h>
#include <sys/un.h>
static constexpr std::string getErrorString(eRequestError error) {
switch (error) {
case GREETD_REQUEST_ERROR_SEND: return "Failed to send payload to greetd";
case GREETD_REQUEST_ERROR_READ: return "Failed to read response from greetd";
case GREETD_REQUEST_ERROR_PARSE: return "Failed to parse response from greetd";
case GREETD_REQUEST_ERROR_FORMAT: return "Invalid greetd response";
default: return "Unknown error";
}
};
static int socketConnect() {
const int FD = socket(AF_UNIX, SOCK_STREAM, 0);
if (FD < 0) {
Debug::log(ERR, "Failed to create socket");
return -1;
}
sockaddr_un serverAddress = {.sun_family = 0};
serverAddress.sun_family = AF_UNIX;
const auto PGREETDSOCK = std::getenv("GREETD_SOCK");
if (PGREETDSOCK == nullptr) {
Debug::log(ERR, "GREETD_SOCK not set!");
return -1;
}
strncpy(serverAddress.sun_path, PGREETDSOCK, sizeof(serverAddress.sun_path) - 1);
if (connect(FD, (sockaddr*)&serverAddress, SUN_LEN(&serverAddress)) < 0) {
Debug::log(ERR, "Failed to connect to greetd socket");
return -1;
}
return FD;
}
// send <payload-length> <payload>
static int sendToSock(int fd, const std::string& PAYLOAD) {
const uint32_t LEN = PAYLOAD.size();
uint32_t wrote = 0;
while (wrote < sizeof(LEN)) {
auto n = write(fd, (char*)&LEN + wrote, sizeof(LEN) - wrote);
if (n < 1) {
Debug::log(ERR, "Failed to write to greetd socket");
return -1;
}
wrote += n;
}
wrote = 0;
while (wrote < LEN) {
auto n = write(fd, PAYLOAD.c_str() + wrote, LEN - wrote);
if (n < 1) {
Debug::log(ERR, "Failed to write to greetd socket");
return -1;
}
wrote += n;
}
return 0;
}
// read <payload-length> <payload>
static std::string readFromSock(int fd) {
uint32_t len = 0;
uint32_t numRead = 0;
while (numRead < sizeof(len)) {
auto n = read(fd, (char*)&len + numRead, sizeof(len) - numRead);
if (n < 1) {
Debug::log(ERR, "Failed to read from greetd socket");
return "";
}
numRead += n;
}
numRead = 0;
std::string msg(len, '\0');
while (numRead < len) {
auto n = read(fd, msg.data() + numRead, len - numRead);
if (n < 1) {
Debug::log(ERR, "Failed to read from greetd socket");
return "";
}
numRead += n;
}
return msg;
}
static bool sendGreetdRequest(int fd, const VGreetdRequest& request) {
if (fd < 0) {
Debug::log(ERR, "[GreetdLogin] Invalid socket fd");
return false;
}
const auto GLZRESULT = glz::write_json(request);
if (!GLZRESULT.has_value()) {
const auto GLZERRORSTR = glz::format_error(GLZRESULT.error());
Debug::log(ERR, "[GreetdLogin] Failed to serialize request: {}", GLZERRORSTR);
return false;
}
if (std::holds_alternative<SGreetdPostAuthMessageResponse>(request))
Debug::log(TRACE, "[GreetdLogin] Request: REDACTED");
else
Debug::log(TRACE, "[GreetdLogin] Request: {}", GLZRESULT.value());
if (sendToSock(fd, GLZRESULT.value()) < 0) {
Debug::log(ERR, "[GreetdLogin] Failed to send payload to greetd");
return false;
}
return true;
}
void CGreetdLogin::init() {
const auto LOGINUSER = g_pConfigManager->getValue<Hyprlang::STRING>("login:user");
m_loginUserName = *LOGINUSER;
if (m_loginUserName.empty()) {
Debug::log(ERR, "[GreetdLogin] No user specified");
m_ok = false;
return;
} else
Debug::log(LOG, "[GreetdLogin] Login user: {}", m_loginUserName);
m_thread = std::thread([this]() {
m_socketFD = socketConnect();
if (m_socketFD < 0) {
Debug::log(ERR, "[GreetdLogin] Failed to connect to greetd socket");
m_ok = false;
}
while (true) {
waitForInput();
processInput();
m_state.inputSubmitted = false;
m_state.input.clear();
}
});
}
std::expected<VGreetdResponse, eRequestError> CGreetdLogin::request(const VGreetdRequest& req) {
if (!sendGreetdRequest(m_socketFD, req)) {
m_ok = false;
return std::unexpected(GREETD_REQUEST_ERROR_SEND);
}
const auto RESPONSESTR = readFromSock(m_socketFD);
if (RESPONSESTR.empty()) {
Debug::log(ERR, "[GreetdLogin] Failed to read response from greetd");
m_ok = false;
return std::unexpected(GREETD_REQUEST_ERROR_READ);
}
Debug::log(TRACE, "[GreetdLogin] Response: {}", RESPONSESTR);
const auto GLZRESULT = glz::read_json<VGreetdResponse>(RESPONSESTR);
if (!GLZRESULT.has_value()) {
const auto GLZERRORSTR = glz::format_error(GLZRESULT.error(), RESPONSESTR);
Debug::log(ERR, "[GreetdLogin] Failed to parse response from greetd: {}", GLZERRORSTR);
m_ok = false;
return std::unexpected(GREETD_REQUEST_ERROR_PARSE);
}
return GLZRESULT.value();
}
void CGreetdLogin::handleResponse(const VGreetdRequest& request, const VGreetdResponse& response) {
if (std::holds_alternative<SGreetdErrorResponse>(response)) {
const auto ERRORRESPONSE = std::get<SGreetdErrorResponse>(response);
m_state.errorType = ERRORRESPONSE.error_type;
m_state.error = ERRORRESPONSE.description;
Debug::log(ERR, "[GreetdLogin] Request failed: {} - {}", (int)m_state.errorType, m_state.error);
// Don't post a fail if this is a response to "cancel_session"
if (!m_state.error.empty() && !std::holds_alternative<SGreetdCancelSession>(request))
g_pAuth->enqueueFail(m_state.error, AUTH_IMPL_GREETD);
// We don't have to cancel if "create_session" failed
if (!std::holds_alternative<SGreetdCreateSession>(request))
cancelSession();
} else if (std::holds_alternative<SGreetdAuthMessageResponse>(response)) {
const auto AUTHMESSAGERESPONSE = std::get<SGreetdAuthMessageResponse>(response);
m_state.authMessageType = AUTHMESSAGERESPONSE.auth_message_type;
m_state.message = AUTHMESSAGERESPONSE.auth_message;
Debug::log(LOG, "[GreetdLogin] Auth message: {} - {}", (int)m_state.authMessageType, m_state.message);
if (m_state.authMessageType == GREETD_AUTH_ERROR && !m_state.message.empty())
g_pAuth->enqueueFail(m_state.message, AUTH_IMPL_GREETD);
} else if (std::holds_alternative<SGreetdSuccessResponse>(response)) {
if (std::holds_alternative<SGreetdCreateSession>(request) || std::holds_alternative<SGreetdPostAuthMessageResponse>(request))
startSessionAfterSuccess();
} else
Debug::log(ERR, "Unknown response from greetd");
}
void CGreetdLogin::startSessionAfterSuccess() {
const auto SELECTEDSESSION = g_pLoginSessionManager->getSelectedLoginSession();
Hyprutils::String::CVarList args(SELECTEDSESSION.exec, 0, ' ');
SGreetdStartSession startSession;
startSession.cmd = std::vector<std::string>{args.begin(), args.end()};
const auto REQUEST = VGreetdRequest{startSession};
// TODO: Is there a response for this? Should we check it?
if (!sendGreetdRequest(m_socketFD, REQUEST))
m_ok = false;
else {
if (g_pHyprlock->m_currentDesktop == "Hyprland")
spawnSync("hyprctl dispatch exit");
else
g_pAuth->enqueueUnlock();
}
}
void CGreetdLogin::cancelSession() {
SGreetdCancelSession cancelSession;
const auto REQUEST = VGreetdRequest{cancelSession};
const auto RESPONSE = request(REQUEST);
if (!RESPONSE.has_value()) {
Debug::log(ERR, "Failed to cancel session: {}", getErrorString(RESPONSE.error()));
return;
}
handleResponse(REQUEST, RESPONSE.value());
m_state.authMessageType = GREETD_INITIAL;
m_state.errorType = GREETD_OK;
}
void CGreetdLogin::createSession() {
if (m_state.authMessageType != GREETD_INITIAL && m_state.errorType != GREETD_ERROR_AUTH)
Debug::log(WARN, "[GreetdLogin] Trying to create a session, but last one still active?");
Debug::log(LOG, "Creating session for user {}", m_loginUserName);
SGreetdCreateSession createSession;
createSession.username = m_loginUserName;
const auto REQUEST = VGreetdRequest{createSession};
const auto RESPONSE = request(REQUEST);
if (!RESPONSE.has_value()) {
Debug::log(ERR, "Failed to create session: {}", getErrorString(RESPONSE.error()));
return;
}
handleResponse(REQUEST, RESPONSE.value());
}
void CGreetdLogin::processInput() {
if (!m_ok) {
g_pAuth->enqueueFail("Greetd login NOK", AUTH_IMPL_GREETD);
return;
}
if (m_state.authMessageType == GREETD_INITIAL)
createSession();
while (m_ok && (m_state.authMessageType == GREETD_AUTH_INFO || m_state.authMessageType == GREETD_AUTH_ERROR)) {
// Empty reply
SGreetdPostAuthMessageResponse postAuthMessageResponse;
const auto REQUEST = VGreetdRequest{postAuthMessageResponse};
const auto RESPONSE = request(REQUEST);
if (!RESPONSE.has_value()) {
Debug::log(ERR, "Failed to create session: {}", getErrorString(RESPONSE.error()));
return;
}
handleResponse(REQUEST, RESPONSE.value());
}
if (m_state.errorType != GREETD_OK) {
// TODO: this error message is not good
Debug::log(LOG, "Empty response to a info message failed!");
return;
}
SGreetdPostAuthMessageResponse postAuthMessageResponse;
postAuthMessageResponse.response = m_state.input;
const auto REQUEST = VGreetdRequest{postAuthMessageResponse};
const auto RESPONSE = request(REQUEST);
if (!RESPONSE.has_value()) {
Debug::log(ERR, "Failed to send auth response: {}", getErrorString(RESPONSE.error()));
return;
}
handleResponse(REQUEST, RESPONSE.value());
};
void CGreetdLogin::waitForInput() {
std::unique_lock<std::mutex> lk(m_state.inputMutex);
m_state.inputSubmittedCondition.wait(lk, [this] { return m_state.inputSubmitted; });
}
void CGreetdLogin::handleInput(const std::string& input) {
std::unique_lock<std::mutex> lk(m_state.inputMutex);
m_state.input = input;
m_state.inputSubmitted = true;
m_state.inputSubmittedCondition.notify_all();
}
bool CGreetdLogin::checkWaiting() {
return m_state.inputSubmitted;
}
std::optional<std::string> CGreetdLogin::getLastFailText() {
if (!m_state.error.empty()) {
return m_state.error;
} else if (m_state.authMessageType == GREETD_AUTH_ERROR)
return m_state.message;
return std::nullopt;
}
std::optional<std::string> CGreetdLogin::getLastPrompt() {
if (!m_state.message.empty())
return m_state.message;
return std::nullopt;
}
void CGreetdLogin::terminate() {
if (m_socketFD > 0)
close(m_socketFD);
m_socketFD = -1;
}

66
src/auth/GreetdLogin.hpp Normal file
View file

@ -0,0 +1,66 @@
#pragma once
#include "Auth.hpp"
#include "GreetdProto.hpp"
#include "../config/LoginSessionManager.hpp"
#include <condition_variable>
#include <string>
#include <expected>
#include <thread>
// INTERNAL
enum eRequestError : uint8_t {
GREETD_REQUEST_ERROR_SEND = 0,
GREETD_REQUEST_ERROR_READ = 1,
GREETD_REQUEST_ERROR_PARSE = 2,
GREETD_REQUEST_ERROR_FORMAT = 3,
};
class CGreetdLogin : public IAuthImplementation {
public:
virtual ~CGreetdLogin() = default;
virtual eAuthImplementations getImplType() {
return AUTH_IMPL_GREETD;
}
virtual void init();
virtual void handleInput(const std::string& input);
virtual bool checkWaiting();
virtual std::optional<std::string> getLastFailText();
virtual std::optional<std::string> getLastPrompt();
virtual void terminate();
struct {
std::string error = "";
eGreetdErrorMessageType errorType = GREETD_OK;
std::string message = "";
eGreetdAuthMessageType authMessageType = GREETD_INITIAL;
std::mutex inputMutex;
std::string input;
bool inputSubmitted = false;
std::condition_variable inputSubmittedCondition;
} m_state;
friend class CAuth;
private:
std::expected<VGreetdResponse, eRequestError> request(const VGreetdRequest& req);
//
void createSession();
void cancelSession();
void recreateSession();
void startSessionAfterSuccess();
void handleResponse(const VGreetdRequest& request, const VGreetdResponse& response);
void processInput();
void waitForInput();
std::thread m_thread;
int m_socketFD = -1;
std::string m_loginUserName = "";
bool m_ok = true;
};

89
src/auth/GreetdProto.hpp Normal file
View file

@ -0,0 +1,89 @@
#include <cstdint>
#include <string>
#include <vector>
#include <variant>
#include <glaze/glaze.hpp>
#include <glaze/util/string_literal.hpp>
// GREETD PROTOCOL
enum eGreetdResponse : uint8_t {
GREETD_RESPONSE_UNKNOWN = 0xff,
GREETD_RESPONSE_SUCCESS = 0,
GREETD_RESPONSE_ERROR = 1,
GREETD_RESPONSE_AUTH = 2,
};
enum eGreetdErrorMessageType : uint8_t {
GREETD_OK = 0,
GREETD_ERROR_AUTH = 1,
GREETD_ERROR = 2,
};
enum eGreetdAuthMessageType : uint8_t {
GREETD_INITIAL = 0,
GREETD_AUTH_VISIBLE = 1,
GREETD_AUTH_SECRET = 2,
GREETD_AUTH_INFO = 3,
GREETD_AUTH_ERROR = 4,
};
// REQUEST TYPES
struct SGreetdCreateSession {
std::string type = "create_session";
std::string username = "";
};
struct SGreetdPostAuthMessageResponse {
std::string type = "post_auth_message_response";
std::string response = "";
};
struct SGreetdStartSession {
std::string type = "start_session";
std::vector<std::string> cmd;
std::vector<std::string> env;
};
struct SGreetdCancelSession {
std::string type = "cancel_session";
};
// RESPONSE TYPES
struct SGreetdErrorResponse {
eGreetdErrorMessageType error_type;
std::string description;
};
struct SGreetdAuthMessageResponse {
eGreetdAuthMessageType auth_message_type;
std::string auth_message;
};
struct SGreetdSuccessResponse {
char DUMMY; // Without any field in SGreetdSuccessResponse, I get unknown_key for "type".
};
// RESPONSE and REQUEST VARIANTS
using VGreetdRequest = std::variant<SGreetdCreateSession, SGreetdPostAuthMessageResponse, SGreetdStartSession, SGreetdCancelSession>;
using VGreetdResponse = std::variant<SGreetdSuccessResponse, SGreetdErrorResponse, SGreetdAuthMessageResponse>;
template <>
struct glz::meta<eGreetdResponse> {
static constexpr auto value = enumerate("success", GREETD_RESPONSE_SUCCESS, "error", GREETD_RESPONSE_ERROR, "auth_message", GREETD_RESPONSE_AUTH);
};
template <>
struct glz::meta<eGreetdAuthMessageType> {
static constexpr auto value = enumerate("visible", GREETD_AUTH_VISIBLE, "secret", GREETD_AUTH_SECRET, "info", GREETD_AUTH_INFO, "error", GREETD_AUTH_ERROR);
};
template <>
struct glz::meta<eGreetdErrorMessageType> {
static constexpr auto value = enumerate("auth_error", GREETD_ERROR_AUTH, "error", GREETD_ERROR);
};
template <>
struct glz::meta<VGreetdResponse> {
static constexpr std::string_view tag = "type";
static constexpr std::array ids{"success", "error", "auth_message"};
};

View file

@ -27,8 +27,8 @@ class ICustomConfigValueData {
class CLayoutValueData : public ICustomConfigValueData {
public:
CLayoutValueData() = default;
virtual ~CLayoutValueData() {};
CLayoutValueData() = default;
virtual ~CLayoutValueData() = default;
virtual eConfigValueDataTypes getDataType() {
return CVD_TYPE_LAYOUT;
@ -61,12 +61,12 @@ class CLayoutValueData : public ICustomConfigValueData {
class CGradientValueData : public ICustomConfigValueData {
public:
CGradientValueData() {};
CGradientValueData() = default;
CGradientValueData(CHyprColor col) {
m_vColors.push_back(col);
updateColorsOk();
};
virtual ~CGradientValueData() {};
virtual ~CGradientValueData() = default;
virtual eConfigValueDataTypes getDataType() {
return CVD_TYPE_GRADIENT;
@ -131,3 +131,9 @@ class CGradientValueData : public ICustomConfigValueData {
return P;
}
};
struct SLoginSessionConfig {
std::string name = "";
std::string exec = "";
std::string desktopFilePath = "";
};

View file

@ -3,6 +3,8 @@
#include "../helpers/MiscFunctions.hpp"
#include "../helpers/Log.hpp"
#include "../core/AnimationManager.hpp"
#include "../config/LoginSessionManager.hpp"
#include <algorithm>
#include <hyprlang.hpp>
#include <hyprutils/string/String.hpp>
#include <hyprutils/path/Path.hpp>
@ -230,6 +232,12 @@ void CConfigManager::init() {
m_config.addConfigValue("animations:enabled", Hyprlang::INT{1});
m_config.addConfigValue("login:user", Hyprlang::STRING{"max"});
m_config.addConfigValue("login:default_session", Hyprlang::STRING{""});
m_config.addSpecialCategory("login-session", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("login-session", "name", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("login-session", "exec", Hyprlang::STRING{""});
m_config.addSpecialCategory("background", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("background", "monitor", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("background", "path", Hyprlang::STRING{""});
@ -327,6 +335,22 @@ void CConfigManager::init() {
SHADOWABLE("label");
CLICKABLE("label");
m_config.addSpecialCategory("session-picker", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("session-picker", "monitor", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("session-picker", "position", LAYOUTCONFIG("0,0"));
m_config.addSpecialConfigValue("session-picker", "size", LAYOUTCONFIG("10%,10%"));
m_config.addSpecialConfigValue("session-picker", "rounding", Hyprlang::INT{0});
m_config.addSpecialConfigValue("session-picker", "border_size", Hyprlang::INT{3});
m_config.addSpecialConfigValue("session-picker", "entry_spacing", Hyprlang::FLOAT{10});
m_config.addSpecialConfigValue("session-picker", "inner_color", Hyprlang::INT{0x00DDDDDD});
m_config.addSpecialConfigValue("session-picker", "selected_color", Hyprlang::INT{0xFFA5A5A5});
m_config.addSpecialConfigValue("session-picker", "border_color", GRADIENTCONFIG("0x00000000"));
m_config.addSpecialConfigValue("session-picker", "selected_border_color", GRADIENTCONFIG("0xff6633ee"));
m_config.addSpecialConfigValue("session-picker", "halign", Hyprlang::STRING{"none"});
m_config.addSpecialConfigValue("session-picker", "valign", Hyprlang::STRING{"none"});
m_config.addSpecialConfigValue("session-picker", "zindex", Hyprlang::INT{0});
SHADOWABLE("session-picker");
m_config.registerHandler(&::handleSource, "source", {.allowFlags = false});
m_config.registerHandler(&::handleBezier, "bezier", {.allowFlags = false});
m_config.registerHandler(&::handleAnimation, "animation", {.allowFlags = false});
@ -522,9 +546,54 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
// clang-format on
}
keys = m_config.listKeysForSpecialCategory("session-picker");
for (auto& k : keys) {
// clang-format off
result.push_back(CConfigManager::SWidgetConfig{
.type="session-picker",
.monitor=std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("session-picker", "monitor", k.c_str())),
.values={
{"position", m_config.getSpecialConfigValue("session-picker", "position", k.c_str())},
{"size", m_config.getSpecialConfigValue("session-picker", "size", k.c_str())},
{"rounding", m_config.getSpecialConfigValue("session-picker", "rounding", k.c_str())},
{"border_size", m_config.getSpecialConfigValue("session-picker", "border_size", k.c_str())},
{"entry_spacing", m_config.getSpecialConfigValue("session-picker", "entry_spacing", k.c_str())},
{"inner_color", m_config.getSpecialConfigValue("session-picker", "inner_color", k.c_str())},
{"selected_color", m_config.getSpecialConfigValue("session-picker", "selected_color", k.c_str())},
{"border_color", m_config.getSpecialConfigValue("session-picker", "border_color", k.c_str())},
{"selected_border_color", m_config.getSpecialConfigValue("session-picker", "selected_border_color", k.c_str())},
{"halign", m_config.getSpecialConfigValue("session-picker", "halign", k.c_str())},
{"valign", m_config.getSpecialConfigValue("session-picker", "valign", k.c_str())},
{"zindex", m_config.getSpecialConfigValue("session-picker", "zindex", k.c_str())},
SHADOWABLE("session-picker"),
}
});
// clang-format on
}
return result;
}
std::vector<SLoginSessionConfig> CConfigManager::getLoginSessionConfigs() {
std::vector<SLoginSessionConfig> result;
//
auto keys = m_config.listKeysForSpecialCategory("login-session");
result.reserve(keys.size());
for (auto& k : keys) {
result.push_back(SLoginSessionConfig{
.name = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("login-session", "name", k.c_str())),
.exec = std::any_cast<Hyprlang::STRING>(m_config.getSpecialConfigValue("login-session", "exec", k.c_str())),
});
}
return result;
}
bool CConfigManager::widgetsContainSessionPicker() {
return !m_config.listKeysForSpecialCategory("session-picker").empty();
}
std::optional<std::string> CConfigManager::handleSource(const std::string& command, const std::string& rawpath) {
if (rawpath.length() < 2) {
Debug::log(ERR, "source= path garbage");

View file

@ -8,6 +8,7 @@
#include <unordered_map>
#include "../defines.hpp"
#include "./ConfigDataValues.hpp"
class CConfigManager {
public:
@ -28,16 +29,22 @@ class CConfigManager {
std::vector<SWidgetConfig> getWidgetConfigs();
std::vector<SLoginSessionConfig> getLoginSessionConfigs();
bool widgetsContainSessionPicker();
std::optional<std::string> handleSource(const std::string&, const std::string&);
std::optional<std::string> handleBezier(const std::string&, const std::string&);
std::optional<std::string> handleAnimation(const std::string&, const std::string&);
std::optional<std::string> handleLoginSession(const std::string&, const std::string&);
std::string configCurrentPath;
Hyprutils::Animation::CAnimationConfigTree m_AnimationTree;
private:
Hyprlang::CConfig m_config;
std::vector<SLoginSessionConfig> m_loginSessions;
Hyprlang::CConfig m_config;
};
inline UP<CConfigManager> g_pConfigManager;

View file

@ -0,0 +1,203 @@
#include "./LoginSessionManager.hpp"
#include "./ConfigManager.hpp"
#include "../core/hyprlock.hpp"
#include "../helpers/MiscFunctions.hpp"
#include "../helpers/Log.hpp"
#include "../renderer/AsyncResourceGatherer.hpp"
#include "../renderer/Renderer.hpp"
#include <fstream>
#include <filesystem>
#include <hyprutils/string/VarList.hpp>
static bool parseDesktopFile(SLoginSessionConfig& sessionConfig) {
RASSERT(!sessionConfig.desktopFilePath.empty(), "Desktop file path is empty");
// read line for line and parse the desktop file naivly
std::ifstream fHandle(sessionConfig.desktopFilePath.c_str());
std::string line;
try {
while (std::getline(fHandle, line)) {
if (line.empty())
continue;
if (line.find("Name=") != std::string::npos)
sessionConfig.name = line.substr(5);
else if (line.find("Exec=") != std::string::npos)
sessionConfig.exec = line.substr(5);
if (!sessionConfig.name.empty() && !sessionConfig.exec.empty())
break;
}
} catch (const std::ifstream::failure& e) {
Debug::log(ERR, "Failed to read session file {}: {}", sessionConfig.desktopFilePath.c_str(), e.what());
return false;
}
if (sessionConfig.name.empty() || sessionConfig.exec.empty()) {
Debug::log(ERR, "Failed to parse session file {}: missing name or exec", sessionConfig.desktopFilePath.c_str());
return false;
}
return true;
}
static std::vector<SLoginSessionConfig> gatherSessions(const std::vector<std::string>& searchPaths) {
std::vector<SLoginSessionConfig> sessions;
for (const auto& DIR : searchPaths) {
if (!std::filesystem::exists(DIR))
continue;
for (const auto& dirEntry : std::filesystem::recursive_directory_iterator{DIR}) {
if (!dirEntry.is_regular_file() || dirEntry.path().extension() != ".desktop")
continue;
SLoginSessionConfig session;
session.desktopFilePath = absolutePath(dirEntry.path().filename(), DIR);
if (!parseDesktopFile(session))
continue;
sessions.emplace_back(session);
}
}
return sessions;
}
CLoginSessionManager::CLoginSessionManager(const std::string& sessionDirs) {
const auto LOGINDEFAULTSESSION = g_pConfigManager->getValue<Hyprlang::STRING>("login:default_session");
Debug::log(LOG, "LoginSessions: Default session: {}, Search directories: {}", std::string{*LOGINDEFAULTSESSION}, sessionDirs);
Hyprutils::String::CVarList sessionDirPaths{sessionDirs, 0, ':', true};
m_loginSessions = gatherSessions(std::vector<std::string>{sessionDirPaths.begin(), sessionDirPaths.end()});
const auto CONFIGUEDSESSIONS = g_pConfigManager->getLoginSessionConfigs();
m_loginSessions.insert(m_loginSessions.end(), CONFIGUEDSESSIONS.begin(), CONFIGUEDSESSIONS.end());
if (const std::string DEFAULTSESSIONSTRING{*LOGINDEFAULTSESSION}; !DEFAULTSESSIONSTRING.empty()) {
bool found = false;
const auto ABSPATH = absolutePath(DEFAULTSESSIONSTRING, "/");
// default session is a name
for (size_t i = 0; i < m_loginSessions.size(); i++) {
if (m_loginSessions[i].name == DEFAULTSESSIONSTRING) {
m_selectedLoginSession = i;
found = true;
break;
}
}
if (!found) {
// default session is a path
for (size_t i = 0; i < m_loginSessions.size(); i++) {
if (m_loginSessions[i].desktopFilePath == ABSPATH) {
m_selectedLoginSession = i;
found = true;
break;
}
}
}
if (!found) {
// default session is a path, but not contained in sessionDirs
SLoginSessionConfig defaultSession;
defaultSession.desktopFilePath = ABSPATH;
if (parseDesktopFile(defaultSession)) {
m_loginSessions.insert(m_loginSessions.begin(), defaultSession);
m_selectedLoginSession = 0;
found = true;
}
}
if (!found)
Debug::log(WARN, "[LoginSessionManager] Default session {} not found", DEFAULTSESSIONSTRING);
}
if (m_loginSessions.empty()) {
Debug::log(CRIT,
"Hyprlock did not find any wayland sessions.\n"
"By default, hyprlock searches /usr/share/wayland-sessions and /usr/local/share/wayland-sessions.\n"
"You can specify the directories hyprlock searches in with the --session-dirs argument followed by a comma seperated list of directories.\n"
"Alternatively, you can specify a session with the login-session hyprlock keyword. Read the wiki for more info\n");
m_loginSessions.emplace_back(SLoginSessionConfig{
.name = "Hyprland",
.exec = "/usr/bin/Hyprland",
});
}
if (!g_pConfigManager->widgetsContainSessionPicker())
m_fixedDefault = true;
requestSessionPickerAssets();
}
void CLoginSessionManager::handleKeyUp() {
if (m_fixedDefault)
return;
if (m_loginSessions.size() > 1) {
if (m_selectedLoginSession > 0)
m_selectedLoginSession--;
else
m_selectedLoginSession = m_loginSessions.size() - 1;
}
}
void CLoginSessionManager::handleKeyDown() {
if (m_fixedDefault)
return;
if (m_loginSessions.size() > 1) {
m_selectedLoginSession++;
if (m_selectedLoginSession >= m_loginSessions.size())
m_selectedLoginSession = 0;
}
}
void CLoginSessionManager::onGotLoginSessionAssetCallback() {
m_renderedSessionNames++;
if (m_renderedSessionNames == m_loginSessionResourceIds.size())
g_pHyprlock->renderAllOutputs();
}
const SLoginSessionConfig& CLoginSessionManager::getSelectedLoginSession() const {
return m_loginSessions[m_selectedLoginSession];
}
size_t CLoginSessionManager::getSelectedLoginSessionIndex() const {
return m_selectedLoginSession;
}
const std::vector<SLoginSessionConfig>& CLoginSessionManager::getLoginSessions() const {
return m_loginSessions;
}
const std::vector<std::string>& CLoginSessionManager::getLoginSessionResourceIds() const {
return m_loginSessionResourceIds;
}
static void sessionNameAssetCallback() {
g_pLoginSessionManager->onGotLoginSessionAssetCallback();
}
void CLoginSessionManager::requestSessionPickerAssets() {
m_loginSessionResourceIds = std::vector<std::string>{m_loginSessions.size(), ""};
for (size_t i = 0; i < m_loginSessions.size(); ++i) {
const auto& SESSIONCONFIG = m_loginSessions[i];
m_loginSessionResourceIds[i] = std::format("session:{}-{}", (uintptr_t)this, SESSIONCONFIG.name);
// request asset preload
CAsyncResourceGatherer::SPreloadRequest request;
request.id = m_loginSessionResourceIds[i];
request.asset = SESSIONCONFIG.name;
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
//request.props["font_family"] = fontFamily;
//request.props["color"] = colorConfig.font;
//request.props["font_size"] = rowHeight;
request.callback = sessionNameAssetCallback;
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
}
}

View file

@ -0,0 +1,37 @@
#pragma once
#include <string>
#include <vector>
#include "../defines.hpp"
#include "./ConfigDataValues.hpp"
class CLoginSessionManager {
public:
CLoginSessionManager(const std::string& sessionDirs);
~CLoginSessionManager() = default;
CLoginSessionManager(const CLoginSessionManager&) = delete;
CLoginSessionManager& operator=(const CLoginSessionManager&) = delete;
CLoginSessionManager(CLoginSessionManager&&) noexcept = delete;
void handleKeyUp();
void handleKeyDown();
void onGotLoginSessionAssetCallback();
const SLoginSessionConfig& getSelectedLoginSession() const;
size_t getSelectedLoginSessionIndex() const;
const std::vector<std::string>& getLoginSessionResourceIds() const;
const std::vector<SLoginSessionConfig>& getLoginSessions() const;
private:
std::vector<SLoginSessionConfig> m_loginSessions;
std::vector<std::string> m_loginSessionResourceIds;
size_t m_renderedSessionNames = 0;
size_t m_selectedLoginSession = 0;
bool m_fixedDefault = false;
void requestSessionPickerAssets();
};
inline UP<CLoginSessionManager> g_pLoginSessionManager;

View file

@ -2,9 +2,11 @@
#include "AnimationManager.hpp"
#include "../helpers/Log.hpp"
#include "../config/ConfigManager.hpp"
#include "../config/LoginSessionManager.hpp"
#include "../renderer/Renderer.hpp"
#include "../auth/Auth.hpp"
#include "../auth/Fingerprint.hpp"
#include "../auth/GreetdLogin.hpp"
#include "Egl.hpp"
#include <hyprutils/memory/UniquePtr.hpp>
#include <sys/wait.h>
@ -35,7 +37,8 @@ static void setMallocThreshold() {
#endif
}
CHyprlock::CHyprlock(const std::string& wlDisplay, const bool immediate, const bool immediateRender) {
CHyprlock::CHyprlock(const std::string& wlDisplay, const bool immediate, const bool immediateRender, const bool greetdLogin, const std::string& sessionDirs) :
m_bGreetdLogin(greetdLogin), m_greetdSessionDirs(sessionDirs) {
setMallocThreshold();
m_sWaylandState.display = wl_display_connect(wlDisplay.empty() ? nullptr : wlDisplay.c_str());
@ -54,7 +57,10 @@ CHyprlock::CHyprlock(const std::string& wlDisplay, const bool immediate, const b
const auto CURRENTDESKTOP = getenv("XDG_CURRENT_DESKTOP");
const auto SZCURRENTD = std::string{CURRENTDESKTOP ? CURRENTDESKTOP : ""};
m_sCurrentDesktop = SZCURRENTD;
m_currentDesktop = SZCURRENTD;
if (greetdLogin && m_greetdSessionDirs.empty())
m_greetdSessionDirs = "/usr/share/wayland-sessions:/usr/local/share/wayland-sessions";
}
CHyprlock::~CHyprlock() {
@ -307,14 +313,15 @@ void CHyprlock::run() {
// gather info about monitors
wl_display_roundtrip(m_sWaylandState.display);
g_pRenderer = makeUnique<CRenderer>();
g_pAuth = makeUnique<CAuth>();
g_pRenderer = makeUnique<CRenderer>();
g_pLoginSessionManager = makeUnique<CLoginSessionManager>(m_greetdSessionDirs);
g_pAuth = makeUnique<CAuth>(m_bGreetdLogin);
g_pAuth->start();
Debug::log(LOG, "Running on {}", m_sCurrentDesktop);
Debug::log(LOG, "Running on {}", m_currentDesktop);
// Hyprland violates the protocol a bit to allow for this.
if (m_sCurrentDesktop != "Hyprland") {
if (m_currentDesktop != "Hyprland") {
while (!g_pRenderer->asyncResourceGatherer->gathered) {
wl_display_flush(m_sWaylandState.display);
if (wl_display_prepare_read(m_sWaylandState.display) == 0) {
@ -499,7 +506,7 @@ void CHyprlock::unlock() {
return;
}
const bool IMMEDIATE = m_sCurrentDesktop != "Hyprland";
const bool IMMEDIATE = m_currentDesktop != "Hyprland";
g_pRenderer->startFadeOut(true, IMMEDIATE);
m_bUnlockedCalled = true;
@ -655,11 +662,15 @@ void CHyprlock::handleKeySym(xkb_keysym_t sym, bool composed) {
m_sPasswordState.passBuffer.pop_back();
m_sPasswordState.passBuffer = m_sPasswordState.passBuffer.substr(0, m_sPasswordState.passBuffer.length() - 1);
}
} else if (SYM == XKB_KEY_Caps_Lock) {
} else if (SYM == XKB_KEY_Caps_Lock)
m_bCapsLock = !m_bCapsLock;
} else if (SYM == XKB_KEY_Num_Lock) {
else if (SYM == XKB_KEY_Num_Lock)
m_bNumLock = !m_bNumLock;
} else {
else if (SYM == XKB_KEY_Up)
g_pLoginSessionManager->handleKeyUp();
else if (SYM == XKB_KEY_Down)
g_pLoginSessionManager->handleKeyDown();
else {
char buf[16] = {0};
int len = (composed) ? xkb_compose_state_get_utf8(g_pSeatManager->m_pXKBComposeState, buf, sizeof(buf)) /* nullbyte */ + 1 :
xkb_keysym_to_utf8(SYM, buf, sizeof(buf)) /* already includes a nullbyte */;

View file

@ -29,7 +29,7 @@ struct SDMABUFModifier {
class CHyprlock {
public:
CHyprlock(const std::string& wlDisplay, const bool immediate, const bool immediateRender);
CHyprlock(const std::string& wlDisplay, const bool immediate, const bool immediateRender, const bool greetdLogin, const std::string& sessionDirs);
~CHyprlock();
void run();
@ -90,7 +90,7 @@ class CHyprlock {
bool m_bImmediateRender = false;
std::string m_sCurrentDesktop = "";
bool m_bGreetdLogin = false;
//
std::chrono::system_clock::time_point m_tGraceEnds;
@ -119,6 +119,9 @@ class CHyprlock {
} dma;
gbm_device* createGBMDevice(drmDevice* dev);
std::string m_greetdSessionDirs = "";
std::string m_currentDesktop = "";
private:
struct {
wl_display* display = nullptr;

View file

@ -2,6 +2,7 @@
#include "config/ConfigManager.hpp"
#include "core/hyprlock.hpp"
#include "helpers/Log.hpp"
#include "helpers/MiscFunctions.hpp"
#include "core/AnimationManager.hpp"
#include <cstddef>
#include <string_view>
@ -9,6 +10,7 @@
void help() {
std::println("Usage: hyprlock [options]\n\n"
"Options:\n"
" -g, --greetd - Start hyprlock for session login via greetd\n"
" -v, --verbose - Enable verbose logging\n"
" -q, --quiet - Disable logging\n"
" -c FILE, --config FILE - Specify config file to use\n"
@ -16,6 +18,7 @@ void help() {
" --immediate - Lock immediately, ignoring any configured grace period\n"
" --immediate-render - Do not wait for resources before drawing the background\n"
" --no-fade-in - Disable the fade-in animation when the lock screen appears\n"
" --session-dirs DIR1:DIR2 - Specify directories to search for session files\n"
" -V, --version - Show version information\n"
" -h, --help - Show this help message");
}
@ -43,9 +46,13 @@ int main(int argc, char** argv, char** envp) {
bool immediate = false;
bool immediateRender = false;
bool noFadeIn = false;
bool greetdLogin = false;
std::vector<std::string> args(argv, argv + argc);
// Used for greetd login
std::string sessionDirs;
for (std::size_t i = 1; i < args.size(); ++i) {
const std::string arg = argv[i];
@ -59,7 +66,16 @@ int main(int argc, char** argv, char** envp) {
return 0;
}
if (arg == "--verbose" || arg == "-v")
if (arg == "--greetd" || arg == "-g") {
greetdLogin = true;
immediate = true;
immediateRender = true;
} else if (arg == "--session-dirs" && i + 1 < (std::size_t)argc) {
if (auto value = parseArg(args, arg, i); value)
sessionDirs = *value;
else
return 1;
} else if (arg == "--verbose" || arg == "-v")
Debug::verbose = true;
else if (arg == "--quiet" || arg == "-q")
@ -111,7 +127,7 @@ int main(int argc, char** argv, char** envp) {
g_pConfigManager->m_AnimationTree.setConfigForNode("fadeIn", false, 0.f, "default");
try {
g_pHyprlock = makeUnique<CHyprlock>(wlDisplay, immediate, immediateRender);
g_pHyprlock = makeUnique<CHyprlock>(wlDisplay, immediate, immediateRender, greetdLogin, sessionDirs);
g_pHyprlock->run();
} catch (const std::exception& ex) {
Debug::log(CRIT, "Hyprlock threw: {}", ex.what());

View file

@ -17,6 +17,7 @@
#include "widgets/Label.hpp"
#include "widgets/Image.hpp"
#include "widgets/Shape.hpp"
#include "widgets/SessionPicker.hpp"
inline const float fullVerts[] = {
1, 0, // top right
@ -420,6 +421,8 @@ std::vector<SP<IWidget>>& CRenderer::getOrCreateWidgetsFor(const CSessionLockSur
createWidget<CShape>(widgets[surf.m_outputID]);
} else if (c.type == "image") {
createWidget<CImage>(widgets[surf.m_outputID]);
} else if (c.type == "session-picker") {
createWidget<CSessionPicker>(widgets[surf.m_outputID]);
} else {
Debug::log(ERR, "Unknown widget type: {}", c.type);
continue;

View file

@ -376,7 +376,7 @@ void CPasswordInputField::updatePlaceholder() {
request.props["font_family"] = fontFamily;
request.props["color"] = colorState.font;
request.props["font_size"] = (int)size->value().y / 4;
request.callback = [REF = m_self] {
request.callback = [REF = m_self]() {
if (const auto SELF = REF.lock(); SELF)
g_pHyprlock->renderOutput(SELF->outputStringPort);
};

View file

@ -0,0 +1,123 @@
#include "SessionPicker.hpp"
#include "../Renderer.hpp"
#include "../../helpers/Log.hpp"
#include "../../helpers/Color.hpp"
#include "../../config/ConfigDataValues.hpp"
#include "../../config/LoginSessionManager.hpp"
#include <algorithm>
#include <hyprlang.hpp>
#include <hyprutils/math/Vector2D.hpp>
#include <stdexcept>
void CSessionPicker::registerSelf(const SP<CSessionPicker>& self) {
m_self = self;
}
void CSessionPicker::configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput) {
m_viewport = pOutput->getViewport();
m_shadow.configure(m_self.lock(), props, m_viewport);
try {
m_configPos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(m_viewport);
m_size = CLayoutValueData::fromAnyPv(props.at("size"))->getAbsolute(m_viewport);
m_rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
m_borderSize = std::any_cast<Hyprlang::INT>(props.at("border_size"));
m_entrySpacing = std::any_cast<Hyprlang::FLOAT>(props.at("entry_spacing"));
m_colorConfig.inner = std::any_cast<Hyprlang::INT>(props.at("inner_color"));
m_colorConfig.selected = std::any_cast<Hyprlang::INT>(props.at("selected_color"));
m_colorConfig.border = *CGradientValueData::fromAnyPv(props.at("border_color"));
m_colorConfig.selectedBorder = *CGradientValueData::fromAnyPv(props.at("selected_border_color"));
m_halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
m_valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
} catch (const std::bad_any_cast& e) {
RASSERT(false, "Failed to construct CSessionPicker: {}", e.what()); //
} catch (const std::out_of_range& e) {
RASSERT(false, "Missing property for CSessionPicker: {}", e.what()); //
}
setupSessionEntryTexts();
}
bool CSessionPicker::draw(const SRenderData& data) {
const size_t SELECTEDENTRYINDEX = g_pLoginSessionManager->getSelectedLoginSessionIndex();
const double PAD = std::abs((m_size.y - m_biggestEntryAssetSize.y) / 2);
const Vector2D SIZE{std::max(m_size.x, m_biggestEntryAssetSize.x + PAD), m_size.y};
const CBox RECTBOX{
posFromHVAlign(m_viewport, SIZE, m_configPos, m_halign, m_valign),
SIZE,
};
const auto ENTRYHEIGHT = RECTBOX.h / (m_loginSessions.size() + 1);
const auto TOPLEFT = RECTBOX.pos() + Vector2D{0.0, RECTBOX.h};
for (size_t i = 0; i < m_loginSessions.size(); ++i) {
auto& sessionEntry = m_loginSessions[i];
const CBox ENTRYBOX{
TOPLEFT.x,
TOPLEFT.y - ENTRYHEIGHT - (i * (ENTRYHEIGHT + m_entrySpacing)),
RECTBOX.w,
ENTRYHEIGHT,
};
const auto ENTRYROUND = roundingForBox(ENTRYBOX, m_rounding);
const bool SELECTED = i == SELECTEDENTRYINDEX;
CHyprColor entryCol;
if (SELECTED)
entryCol = CHyprColor{m_colorConfig.selected.asRGB(), m_colorConfig.selected.a * data.opacity};
else
entryCol = CHyprColor{m_colorConfig.inner.asRGB(), m_colorConfig.inner.a * data.opacity};
g_pRenderer->renderRect(ENTRYBOX, entryCol, ENTRYROUND);
if (m_borderSize > 0) {
const CBox ENTRYBORDERBOX{
ENTRYBOX.pos() - Vector2D{m_borderSize, m_borderSize},
ENTRYBOX.size() + Vector2D{2 * m_borderSize, 2 * m_borderSize},
};
const auto ENTRYBORDERROUND = roundingForBorderBox(ENTRYBORDERBOX, m_rounding, m_borderSize);
g_pRenderer->renderBorder(ENTRYBORDERBOX, (SELECTED) ? m_colorConfig.selectedBorder : m_colorConfig.border, m_borderSize, ENTRYBORDERROUND, data.opacity);
}
if (!sessionEntry.m_textAsset) {
sessionEntry.m_textAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(sessionEntry.m_textResourceID);
if (sessionEntry.m_textAsset)
m_biggestEntryAssetSize = Vector2D{
std::max<double>(m_biggestEntryAssetSize.x, sessionEntry.m_textAsset->texture.m_vSize.x),
sessionEntry.m_textAsset->texture.m_vSize.y,
};
}
if (sessionEntry.m_textAsset) {
const CBox ASSETBOXCENTERED{
ENTRYBOX.pos() +
Vector2D{
(ENTRYBOX.size().x / 2) - (sessionEntry.m_textAsset->texture.m_vSize.x / 2),
(ENTRYBOX.size().y / 2) - (sessionEntry.m_textAsset->texture.m_vSize.y / 2),
},
sessionEntry.m_textAsset->texture.m_vSize,
};
g_pRenderer->renderTexture(ASSETBOXCENTERED, sessionEntry.m_textAsset->texture, data.opacity);
}
}
return false; // rely on the asset update callback in case m_textAsset is a nullptr
}
void CSessionPicker::setupSessionEntryTexts() {
const auto& LOGINSESSIONS = g_pLoginSessionManager->getLoginSessions();
const auto& LOGINSESSIONRESOURCEIDS = g_pLoginSessionManager->getLoginSessionResourceIds();
RASSERT(LOGINSESSIONS.size() == LOGINSESSIONRESOURCEIDS.size(), "Login session resource IDs size does not match login sessions size");
m_loginSessions.resize(LOGINSESSIONS.size());
for (size_t i = 0; i < LOGINSESSIONS.size(); i++) {
m_loginSessions[i] = {
.m_loginSession = LOGINSESSIONS[i],
.m_textResourceID = LOGINSESSIONRESOURCEIDS[i],
};
};
}

View file

@ -0,0 +1,61 @@
#pragma once
#include "IWidget.hpp"
#include "Shadowable.hpp"
#include "../../config/ConfigDataValues.hpp"
#include "../../helpers/Color.hpp"
#include <hyprutils/math/Vector2D.hpp>
#include <string>
#include <any>
#include <vector>
struct SPreloadedAsset;
class CSessionLockSurface;
class CSessionPicker : public IWidget {
public:
struct SSessionAsset {
SLoginSessionConfig m_loginSession;
std::string m_textResourceID;
SPreloadedAsset* m_textAsset = nullptr;
};
CSessionPicker() = default;
~CSessionPicker() = default;
void registerSelf(const SP<CSessionPicker>& self);
virtual void configure(const std::unordered_map<std::string, std::any>& props, const SP<COutput>& pOutput);
virtual bool draw(const SRenderData& data);
void onGotSessionEntryAsset(const std::string& sessionName);
private:
void setupSessionEntryTexts();
WP<CSessionPicker> m_self;
std::vector<SSessionAsset> m_loginSessions;
Vector2D m_viewport;
Vector2D m_configPos;
Vector2D m_size;
std::string m_halign = "";
std::string m_valign = "";
int m_rounding = -1;
int m_borderSize = -1;
int m_entryHeight = -1;
int m_entrySpacing = -1;
Vector2D m_biggestEntryAssetSize;
struct {
CHyprColor inner;
CHyprColor selected;
CGradientValueData border;
CGradientValueData selectedBorder;
} m_colorConfig;
CShadowable m_shadow;
bool m_updateShadow = true;
};