mirror of
https://github.com/hyprwm/hyprlock.git
synced 2024-12-22 13:29:48 +01:00
core/input: add feedback for password verification
This commit is contained in:
parent
96f2818915
commit
9074ff702d
6 changed files with 144 additions and 40 deletions
|
@ -1,9 +1,12 @@
|
||||||
#include "Password.hpp"
|
#include "Password.hpp"
|
||||||
|
#include "hyprlock.hpp"
|
||||||
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <security/pam_appl.h>
|
#include <security/pam_appl.h>
|
||||||
#include <security/pam_misc.h>
|
#include <security/pam_misc.h>
|
||||||
|
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
struct pam_response* reply;
|
struct pam_response* reply;
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -12,14 +15,27 @@ int conv(int num_msg, const struct pam_message** msg, struct pam_response** resp
|
||||||
return PAM_SUCCESS;
|
return PAM_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
CPassword::SVerificationResult CPassword::verify(const std::string& pass) {
|
static void passwordCheckTimerCallback(std::shared_ptr<CTimer> self, void* data) {
|
||||||
|
g_pHyprlock->onPasswordCheckTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<CPassword::SVerificationResult> CPassword::verify(const std::string& pass) {
|
||||||
|
|
||||||
|
std::shared_ptr<CPassword::SVerificationResult> result = std::make_shared<CPassword::SVerificationResult>(false);
|
||||||
|
|
||||||
|
std::thread([this, result, pass]() {
|
||||||
const pam_conv localConv = {conv, NULL};
|
const pam_conv localConv = {conv, NULL};
|
||||||
pam_handle_t* handle = NULL;
|
pam_handle_t* handle = NULL;
|
||||||
|
|
||||||
int ret = pam_start("su", getlogin(), &localConv, &handle);
|
int ret = pam_start("su", getlogin(), &localConv, &handle);
|
||||||
|
|
||||||
if (ret != PAM_SUCCESS)
|
if (ret != PAM_SUCCESS) {
|
||||||
return {false, "pam_start failed"};
|
result->success = false;
|
||||||
|
result->failReason = "pam_start failed";
|
||||||
|
result->realized = true;
|
||||||
|
g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
reply = (struct pam_response*)malloc(sizeof(struct pam_response));
|
reply = (struct pam_response*)malloc(sizeof(struct pam_response));
|
||||||
|
|
||||||
|
@ -27,10 +43,21 @@ CPassword::SVerificationResult CPassword::verify(const std::string& pass) {
|
||||||
reply->resp_retcode = 0;
|
reply->resp_retcode = 0;
|
||||||
ret = pam_authenticate(handle, 0);
|
ret = pam_authenticate(handle, 0);
|
||||||
|
|
||||||
if (ret != PAM_SUCCESS)
|
if (ret != PAM_SUCCESS) {
|
||||||
return {false, ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed"};
|
result->success = false;
|
||||||
|
result->failReason = ret == PAM_AUTH_ERR ? "Authentication failed" : "pam_authenticate failed";
|
||||||
|
result->realized = true;
|
||||||
|
g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ret = pam_end(handle, ret);
|
ret = pam_end(handle, ret);
|
||||||
|
|
||||||
return {true, "Successfully authenticated"};
|
result->success = true;
|
||||||
|
result->failReason = "Successfully authenticated";
|
||||||
|
result->realized = true;
|
||||||
|
g_pHyprlock->addTimer(std::chrono::milliseconds(1), passwordCheckTimerCallback, nullptr);
|
||||||
|
}).detach();
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
|
@ -2,15 +2,17 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
class CPassword {
|
class CPassword {
|
||||||
public:
|
public:
|
||||||
struct SVerificationResult {
|
struct SVerificationResult {
|
||||||
|
std::atomic<bool> realized = false;
|
||||||
bool success = false;
|
bool success = false;
|
||||||
std::string failReason = "";
|
std::string failReason = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
SVerificationResult verify(const std::string& pass);
|
std::shared_ptr<SVerificationResult> verify(const std::string& pass);
|
||||||
};
|
};
|
||||||
|
|
||||||
inline std::unique_ptr<CPassword> g_pPassword = std::make_unique<CPassword>();
|
inline std::unique_ptr<CPassword> g_pPassword = std::make_unique<CPassword>();
|
|
@ -405,6 +405,27 @@ static const ext_session_lock_v1_listener sessionLockListener = {
|
||||||
|
|
||||||
// end session_lock
|
// end session_lock
|
||||||
|
|
||||||
|
void CHyprlock::onPasswordCheckTimer() {
|
||||||
|
// check result
|
||||||
|
if (m_sPasswordState.result->success) {
|
||||||
|
unlockSession();
|
||||||
|
} else {
|
||||||
|
Debug::log(LOG, "Authentication failed: {}", m_sPasswordState.result->failReason);
|
||||||
|
m_sPasswordState.lastFailReason = m_sPasswordState.result->failReason;
|
||||||
|
m_sPasswordState.passBuffer = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
m_sPasswordState.result.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CHyprlock::passwordCheckWaiting() {
|
||||||
|
return m_sPasswordState.result.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> CHyprlock::passwordLastFailReason() {
|
||||||
|
return m_sPasswordState.lastFailReason;
|
||||||
|
}
|
||||||
|
|
||||||
void CHyprlock::onKey(uint32_t key) {
|
void CHyprlock::onKey(uint32_t key) {
|
||||||
const auto SYM = xkb_state_key_get_one_sym(m_pXKBState, key + 8);
|
const auto SYM = xkb_state_key_get_one_sym(m_pXKBState, key + 8);
|
||||||
|
|
||||||
|
@ -414,14 +435,7 @@ void CHyprlock::onKey(uint32_t key) {
|
||||||
} else if (SYM == XKB_KEY_Return) {
|
} else if (SYM == XKB_KEY_Return) {
|
||||||
Debug::log(LOG, "Authenticating");
|
Debug::log(LOG, "Authenticating");
|
||||||
|
|
||||||
const auto RESULT = g_pPassword->verify(m_sPasswordState.passBuffer);
|
m_sPasswordState.result = g_pPassword->verify(m_sPasswordState.passBuffer);
|
||||||
|
|
||||||
Debug::log(LOG, "Password auth result: {}", RESULT.failReason);
|
|
||||||
|
|
||||||
if (RESULT.success)
|
|
||||||
unlockSession();
|
|
||||||
|
|
||||||
m_sPasswordState.passBuffer = "";
|
|
||||||
} else {
|
} else {
|
||||||
char buf[16] = {0};
|
char buf[16] = {0};
|
||||||
int len = xkb_keysym_to_utf8(SYM, buf, 16);
|
int len = xkb_keysym_to_utf8(SYM, buf, 16);
|
||||||
|
|
|
@ -7,10 +7,12 @@
|
||||||
#include "Output.hpp"
|
#include "Output.hpp"
|
||||||
#include "CursorShape.hpp"
|
#include "CursorShape.hpp"
|
||||||
#include "Timer.hpp"
|
#include "Timer.hpp"
|
||||||
|
#include "Password.hpp"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include <xkbcommon/xkbcommon.h>
|
#include <xkbcommon/xkbcommon.h>
|
||||||
|
|
||||||
|
@ -32,6 +34,9 @@ class CHyprlock {
|
||||||
void unlockSession();
|
void unlockSession();
|
||||||
|
|
||||||
void onKey(uint32_t key);
|
void onKey(uint32_t key);
|
||||||
|
void onPasswordCheckTimer();
|
||||||
|
bool passwordCheckWaiting();
|
||||||
|
std::optional<std::string> passwordLastFailReason();
|
||||||
|
|
||||||
size_t getPasswordBufferLen();
|
size_t getPasswordBufferLen();
|
||||||
|
|
||||||
|
@ -70,6 +75,8 @@ class CHyprlock {
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
std::string passBuffer = "";
|
std::string passBuffer = "";
|
||||||
|
std::shared_ptr<CPassword::SVerificationResult> result;
|
||||||
|
std::optional<std::string> lastFailReason;
|
||||||
} m_sPasswordState;
|
} m_sPasswordState;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|
|
@ -23,7 +23,7 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport, const std::un
|
||||||
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
|
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
|
||||||
request.props["font_family"] = std::string{"Sans"};
|
request.props["font_family"] = std::string{"Sans"};
|
||||||
request.props["color"] = CColor{1.0 - font.r, 1.0 - font.g, 1.0 - font.b, 0.5};
|
request.props["color"] = CColor{1.0 - font.r, 1.0 - font.g, 1.0 - font.b, 0.5};
|
||||||
request.props["font_size"] = 12;
|
request.props["font_size"] = (int)size.y / 4;
|
||||||
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
|
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,13 +86,20 @@ bool CPasswordInputField::draw(const SRenderData& data) {
|
||||||
CBox inputFieldBox = {pos, size};
|
CBox inputFieldBox = {pos, size};
|
||||||
CBox outerBox = {pos - Vector2D{out_thick, out_thick}, size + Vector2D{out_thick * 2, out_thick * 2}};
|
CBox outerBox = {pos - Vector2D{out_thick, out_thick}, size + Vector2D{out_thick * 2, out_thick * 2}};
|
||||||
|
|
||||||
|
bool forceReload = false;
|
||||||
|
|
||||||
updateFade();
|
updateFade();
|
||||||
updateDots();
|
updateDots();
|
||||||
|
updateFailTex();
|
||||||
|
|
||||||
|
float passAlpha = g_pHyprlock->passwordCheckWaiting() ? 0.5 : 1.0;
|
||||||
|
|
||||||
CColor outerCol = outer;
|
CColor outerCol = outer;
|
||||||
outer.a = fade.a * data.opacity;
|
outer.a = fade.a * data.opacity;
|
||||||
CColor innerCol = inner;
|
CColor innerCol = inner;
|
||||||
innerCol.a = fade.a * data.opacity;
|
innerCol.a = fade.a * data.opacity;
|
||||||
|
CColor fontCol = font;
|
||||||
|
fontCol.a *= fade.a * data.opacity * passAlpha;
|
||||||
|
|
||||||
g_pRenderer->renderRect(outerBox, outerCol, outerBox.h / 2.0);
|
g_pRenderer->renderRect(outerBox, outerCol, outerBox.h / 2.0);
|
||||||
g_pRenderer->renderRect(inputFieldBox, innerCol, inputFieldBox.h / 2.0);
|
g_pRenderer->renderRect(inputFieldBox, innerCol, inputFieldBox.h / 2.0);
|
||||||
|
@ -103,7 +110,6 @@ bool CPasswordInputField::draw(const SRenderData& data) {
|
||||||
for (size_t i = 0; i < std::floor(dots.currentAmount); ++i) {
|
for (size_t i = 0; i < std::floor(dots.currentAmount); ++i) {
|
||||||
Vector2D currentPos = inputFieldBox.pos() + Vector2D{PASS_SPACING * 2, inputFieldBox.h / 2.f - PASS_SIZE / 2.f} + Vector2D{(PASS_SIZE + PASS_SPACING) * i, 0};
|
Vector2D currentPos = inputFieldBox.pos() + Vector2D{PASS_SPACING * 2, inputFieldBox.h / 2.f - PASS_SIZE / 2.f} + Vector2D{(PASS_SIZE + PASS_SPACING) * i, 0};
|
||||||
CBox box{currentPos, Vector2D{PASS_SIZE, PASS_SIZE}};
|
CBox box{currentPos, Vector2D{PASS_SIZE, PASS_SIZE}};
|
||||||
CColor fontCol = font;
|
|
||||||
g_pRenderer->renderRect(box, fontCol, PASS_SIZE / 2.0);
|
g_pRenderer->renderRect(box, fontCol, PASS_SIZE / 2.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +117,6 @@ bool CPasswordInputField::draw(const SRenderData& data) {
|
||||||
Vector2D currentPos =
|
Vector2D currentPos =
|
||||||
inputFieldBox.pos() + Vector2D{PASS_SPACING * 2, inputFieldBox.h / 2.f - PASS_SIZE / 2.f} + Vector2D{(PASS_SIZE + PASS_SPACING) * std::floor(dots.currentAmount), 0};
|
inputFieldBox.pos() + Vector2D{PASS_SPACING * 2, inputFieldBox.h / 2.f - PASS_SIZE / 2.f} + Vector2D{(PASS_SIZE + PASS_SPACING) * std::floor(dots.currentAmount), 0};
|
||||||
CBox box{currentPos, Vector2D{PASS_SIZE, PASS_SIZE}};
|
CBox box{currentPos, Vector2D{PASS_SIZE, PASS_SIZE}};
|
||||||
CColor fontCol = font;
|
|
||||||
fontCol.a = (dots.currentAmount - std::floor(dots.currentAmount)) * data.opacity;
|
fontCol.a = (dots.currentAmount - std::floor(dots.currentAmount)) * data.opacity;
|
||||||
g_pRenderer->renderRect(box, fontCol, PASS_SIZE / 2.0);
|
g_pRenderer->renderRect(box, fontCol, PASS_SIZE / 2.0);
|
||||||
}
|
}
|
||||||
|
@ -119,16 +124,60 @@ bool CPasswordInputField::draw(const SRenderData& data) {
|
||||||
const auto PASSLEN = g_pHyprlock->getPasswordBufferLen();
|
const auto PASSLEN = g_pHyprlock->getPasswordBufferLen();
|
||||||
|
|
||||||
if (PASSLEN == 0 && !placeholder.resourceID.empty()) {
|
if (PASSLEN == 0 && !placeholder.resourceID.empty()) {
|
||||||
|
SPreloadedAsset* currAsset = nullptr;
|
||||||
|
|
||||||
|
if (!placeholder.failID.empty()) {
|
||||||
|
if (!placeholder.failAsset)
|
||||||
|
placeholder.failAsset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.failID);
|
||||||
|
|
||||||
|
currAsset = placeholder.failAsset;
|
||||||
|
} else {
|
||||||
if (!placeholder.asset)
|
if (!placeholder.asset)
|
||||||
placeholder.asset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.resourceID);
|
placeholder.asset = g_pRenderer->asyncResourceGatherer->getAssetByID(placeholder.resourceID);
|
||||||
|
|
||||||
if (placeholder.asset) {
|
currAsset = placeholder.asset;
|
||||||
Vector2D pos = outerBox.pos() + outerBox.size() / 2.f;
|
|
||||||
pos = pos - placeholder.asset->texture.m_vSize / 2.f;
|
|
||||||
CBox textbox{pos, placeholder.asset->texture.m_vSize};
|
|
||||||
g_pRenderer->renderTexture(textbox, placeholder.asset->texture, data.opacity * fade.a, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return dots.currentAmount != PASSLEN || data.opacity < 1.0 || fade.a < 1.0;
|
if (currAsset) {
|
||||||
|
Vector2D pos = outerBox.pos() + outerBox.size() / 2.f;
|
||||||
|
pos = pos - currAsset->texture.m_vSize / 2.f;
|
||||||
|
CBox textbox{pos, currAsset->texture.m_vSize};
|
||||||
|
g_pRenderer->renderTexture(textbox, currAsset->texture, data.opacity * fade.a, 0);
|
||||||
|
} else
|
||||||
|
forceReload = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dots.currentAmount != PASSLEN || data.opacity < 1.0 || fade.a < 1.0 || forceReload;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPasswordInputField::updateFailTex() {
|
||||||
|
const auto FAIL = g_pHyprlock->passwordLastFailReason();
|
||||||
|
|
||||||
|
if (g_pHyprlock->passwordCheckWaiting())
|
||||||
|
placeholder.canGetNewFail = true;
|
||||||
|
|
||||||
|
if (g_pHyprlock->getPasswordBufferLen() != 0) {
|
||||||
|
if (placeholder.failAsset) {
|
||||||
|
g_pRenderer->asyncResourceGatherer->unloadAsset(placeholder.failAsset);
|
||||||
|
placeholder.failAsset = nullptr;
|
||||||
|
placeholder.failID = "";
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!FAIL.has_value() || !placeholder.canGetNewFail)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// query
|
||||||
|
CAsyncResourceGatherer::SPreloadRequest request;
|
||||||
|
request.id = "input-error:" + std::to_string((uintptr_t)this) + ",time:" + std::to_string(time(nullptr));
|
||||||
|
placeholder.failID = request.id;
|
||||||
|
request.asset = "<span style=\"italic\">" + FAIL.value() + "</span>";
|
||||||
|
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
|
||||||
|
request.props["font_family"] = std::string{"Sans"};
|
||||||
|
request.props["color"] = CColor{1.0 - font.r, 1.0 - font.g, 1.0 - font.b, 0.5};
|
||||||
|
request.props["font_size"] = (int)size.y / 4;
|
||||||
|
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
|
||||||
|
|
||||||
|
placeholder.canGetNewFail = false;
|
||||||
}
|
}
|
|
@ -19,6 +19,7 @@ class CPasswordInputField : public IWidget {
|
||||||
private:
|
private:
|
||||||
void updateDots();
|
void updateDots();
|
||||||
void updateFade();
|
void updateFade();
|
||||||
|
void updateFailTex();
|
||||||
|
|
||||||
Vector2D size;
|
Vector2D size;
|
||||||
Vector2D pos;
|
Vector2D pos;
|
||||||
|
@ -43,6 +44,10 @@ class CPasswordInputField : public IWidget {
|
||||||
struct {
|
struct {
|
||||||
std::string resourceID = "";
|
std::string resourceID = "";
|
||||||
SPreloadedAsset* asset = nullptr;
|
SPreloadedAsset* asset = nullptr;
|
||||||
|
|
||||||
|
std::string failID = "";
|
||||||
|
SPreloadedAsset* failAsset = nullptr;
|
||||||
|
bool canGetNewFail = true;
|
||||||
} placeholder;
|
} placeholder;
|
||||||
|
|
||||||
bool fadeOnEmpty;
|
bool fadeOnEmpty;
|
||||||
|
|
Loading…
Reference in a new issue