179 lines
4.4 KiB
C++
179 lines
4.4 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
#include "bubblecontroller.h"
|
|
|
|
#include "userconfig.h"
|
|
|
|
#include <KLocalizedString>
|
|
|
|
#include <QJsonObject>
|
|
|
|
namespace
|
|
{
|
|
// How long a result stays up before the bubble closes: long enough to read
|
|
// (on success after the face has resolved, about a second), short enough not
|
|
// to be in the way.
|
|
constexpr int SuccessHoldMs = 1500;
|
|
constexpr int FailureHoldMs = 1700;
|
|
constexpr int LockoutHoldMs = 3000;
|
|
// A scan that never reports back (the daemon went away half way) must not
|
|
// leave the bubble up for good.
|
|
constexpr int ScanWatchdogMs = 30000;
|
|
} // namespace
|
|
|
|
BubbleController::BubbleController(UserConfig *config, QObject *parent)
|
|
: QObject(parent)
|
|
, m_config(config)
|
|
{
|
|
m_hide.setSingleShot(true);
|
|
connect(&m_hide, &QTimer::timeout, this, &BubbleController::dismiss);
|
|
connect(m_config, &UserConfig::changed, this, &BubbleController::styleChanged);
|
|
connect(m_config, &UserConfig::changed, this, &BubbleController::paceChanged);
|
|
}
|
|
|
|
qreal BubbleController::pace() const
|
|
{
|
|
return m_config->pace();
|
|
}
|
|
|
|
QString BubbleController::style() const
|
|
{
|
|
return m_config->bubbleStyle();
|
|
}
|
|
|
|
bool BubbleController::enabled() const
|
|
{
|
|
return m_config->bubble();
|
|
}
|
|
|
|
void BubbleController::setPhase(const QString &phase)
|
|
{
|
|
if (m_phase == phase) {
|
|
return;
|
|
}
|
|
m_phase = phase;
|
|
if (phase != u"hidden" && !m_shown) {
|
|
m_shown = true;
|
|
Q_EMIT shownChanged();
|
|
}
|
|
Q_EMIT phaseChanged();
|
|
}
|
|
|
|
void BubbleController::setMessage(const QString &message)
|
|
{
|
|
if (m_message != message) {
|
|
m_message = message;
|
|
Q_EMIT messageChanged();
|
|
}
|
|
}
|
|
|
|
void BubbleController::scanStarted()
|
|
{
|
|
if (!enabled()) {
|
|
return;
|
|
}
|
|
m_hide.start(ScanWatchdogMs);
|
|
setMessage({});
|
|
if (m_faceSeen) {
|
|
m_faceSeen = false;
|
|
Q_EMIT faceSeenChanged();
|
|
}
|
|
setPhase(QStringLiteral("scanning"));
|
|
}
|
|
|
|
void BubbleController::faceFound()
|
|
{
|
|
if (m_phase == u"scanning" && !m_faceSeen) {
|
|
m_faceSeen = true;
|
|
Q_EMIT faceSeenChanged();
|
|
}
|
|
}
|
|
|
|
void BubbleController::hint(const QString &hint)
|
|
{
|
|
if (m_phase != u"scanning") {
|
|
return;
|
|
}
|
|
if (hint == u"blink") {
|
|
setMessage(i18n("Blink once"));
|
|
} else if (hint == u"look") {
|
|
setMessage(i18n("Look at the screen"));
|
|
} else if (hint == u"closer") {
|
|
setMessage(i18n("Move closer"));
|
|
} else if (hint == u"light") {
|
|
setMessage(i18n("Too dark"));
|
|
}
|
|
}
|
|
|
|
void BubbleController::succeeded()
|
|
{
|
|
if (m_phase == u"hidden") {
|
|
return;
|
|
}
|
|
setMessage({});
|
|
setPhase(QStringLiteral("success"));
|
|
m_hide.start(int(SuccessHoldMs * pace()));
|
|
}
|
|
|
|
void BubbleController::failed(const QString &reason, qint64 lockout)
|
|
{
|
|
if (m_phase == u"hidden") {
|
|
return;
|
|
}
|
|
if (lockout > 0 || reason == u"lockout") {
|
|
setMessage(i18n("Use your password"));
|
|
setPhase(QStringLiteral("lockout"));
|
|
m_hide.start(int(LockoutHoldMs * pace()));
|
|
return;
|
|
}
|
|
if (reason == u"mismatch" || reason == u"spoof") {
|
|
setMessage(i18n("Not recognized"));
|
|
} else if (reason == u"liveness") {
|
|
setMessage(i18n("Try again and blink"));
|
|
} else if (reason == u"camera") {
|
|
setMessage(i18n("Camera unavailable"));
|
|
} else {
|
|
// Nobody there, a head turned away, the scan cancelled because the
|
|
// password was typed: nothing worth saying. The bubble just goes.
|
|
setMessage({});
|
|
setPhase(QStringLiteral("hidden"));
|
|
m_hide.stop();
|
|
return;
|
|
}
|
|
setPhase(QStringLiteral("failure"));
|
|
m_hide.start(int(FailureHoldMs * pace()));
|
|
}
|
|
|
|
void BubbleController::dismiss()
|
|
{
|
|
m_hide.stop();
|
|
setPhase(QStringLiteral("hidden"));
|
|
}
|
|
|
|
void BubbleController::closed()
|
|
{
|
|
if (m_phase == u"hidden" && m_shown) {
|
|
m_shown = false;
|
|
Q_EMIT shownChanged();
|
|
}
|
|
}
|
|
|
|
void BubbleController::daemonEvent(const QJsonObject &event)
|
|
{
|
|
if (!m_config->bubbleForPrompts()) {
|
|
return;
|
|
}
|
|
const QString state = event.value(u"state").toString();
|
|
if (state == u"start") {
|
|
scanStarted();
|
|
} else if (state == u"face") {
|
|
faceFound();
|
|
} else if (state == u"hint") {
|
|
hint(event.value(u"hint").toString());
|
|
} else if (state == u"success") {
|
|
succeeded();
|
|
} else if (state == u"failure") {
|
|
failed(event.value(u"reason").toString(), qint64(event.value(u"lockout").toDouble()));
|
|
}
|
|
}
|