feat: add plasma-face-unlock

This commit is contained in:
Felitendo committed 2026-09-22 19:39:04 +02:00
commit f671acc93b
105 files changed
+13862

No files matched your search

+126
View File
@@ -0,0 +1,126 @@
// SPDX-License-Identifier: GPL-3.0-or-later
//
// plasma-face-unlock-agent: the part in the user's session.
//
// (no arguments) stay in the background: unlock the lock screen by face,
// and show the bubble for every scan
// --enroll open the window that sets up a face
// --demo play the bubble's animations once, for trying out a
// style (--bubble-style minimal) and for screenshots
#include "agentsocket.h"
#include "bubblecontroller.h"
#include "bubblewindow.h"
#include "enrollcontroller.h"
#include "lockcontroller.h"
#include "userconfig.h"
#include "buildconfig.h"
#include <KLocalizedQmlContext>
#include <KLocalizedString>
#include <QCommandLineParser>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlEngine>
#include <QTimer>
#include <unistd.h>
namespace
{
int runEnroll(QGuiApplication &app, const QString &name)
{
app.setQuitOnLastWindowClosed(true);
EnrollController controller;
controller.setName(name);
QQmlApplicationEngine engine;
KLocalization::setupLocalizedContext(&engine);
engine.setInitialProperties({{QStringLiteral("controller"), QVariant::fromValue(&controller)}});
engine.loadFromModule(QStringLiteral("PlasmaFaceUnlock"), QStringLiteral("Enroll"));
if (engine.rootObjects().isEmpty()) {
return 2;
}
int code = 1;
QObject::connect(&controller, &EnrollController::completed, &app, [&code](bool ok) {
code = ok ? 0 : 1;
QCoreApplication::quit();
});
app.exec();
return controller.state() == u"done" ? 0 : code;
}
// The whole life of a bubble, twice: a face that is recognised after a blink,
// then one that is not.
void scheduleDemo(BubbleController *bubble)
{
const QList<std::pair<int, std::function<void()>>> steps = {
{300, [bubble] { bubble->scanStarted(); }},
{900, [bubble] { bubble->faceFound(); }},
{1900, [bubble] { bubble->hint(QStringLiteral("blink")); }},
{3000, [bubble] { bubble->succeeded(); }},
{5600, [bubble] { bubble->scanStarted(); }},
{6200, [bubble] { bubble->faceFound(); }},
{7800, [bubble] { bubble->failed(QStringLiteral("mismatch")); }},
{11000, [] { QCoreApplication::quit(); }},
};
for (const auto &[at, what] : steps) {
QTimer::singleShot(at, bubble, what);
}
}
} // namespace
int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
app.setApplicationName(QStringLiteral("plasma-face-unlock-agent"));
app.setApplicationVersion(QStringLiteral(PFU_VERSION));
app.setDesktopFileName(QStringLiteral("io.github.loonixtools.plasma-face-unlock-agent"));
KLocalizedString::setApplicationDomain(PFU_NAME);
QCommandLineParser parser;
parser.setApplicationDescription(i18n("Face unlock for KDE Plasma"));
parser.addHelpOption();
parser.addVersionOption();
const QCommandLineOption enrollOpt(QStringLiteral("enroll"), i18n("Set up a face."));
const QCommandLineOption nameOpt(QStringLiteral("name"), i18n("What to call the new face."), QStringLiteral("name"));
const QCommandLineOption demoOpt(QStringLiteral("demo"), i18n("Play the bubble's animations once."));
// Not "--style": QGuiApplication takes that one for itself.
const QCommandLineOption styleOpt(QStringLiteral("bubble-style"), i18n("Bubble style for the demo: full or minimal."), QStringLiteral("style"));
parser.addOptions({enrollOpt, nameOpt, demoOpt, styleOpt});
parser.process(app);
if (parser.isSet(enrollOpt)) {
QString name = parser.value(nameOpt);
if (name.isEmpty()) {
name = qEnvironmentVariable("USER");
}
return runEnroll(app, name);
}
app.setQuitOnLastWindowClosed(false);
QQmlEngine engine;
KLocalization::setupLocalizedContext(&engine);
UserConfig config;
if (parser.isSet(styleOpt)) {
config.overrideStyle(parser.value(styleOpt));
}
BubbleController bubble(&config);
BubbleWindow window(&engine, &bubble);
if (parser.isSet(demoOpt)) {
scheduleDemo(&bubble);
return app.exec();
}
AgentSocket socket;
socket.listen();
QObject::connect(&socket, &AgentSocket::scanEvent, &bubble, &BubbleController::daemonEvent);
LockController lock(&bubble, &config);
return app.exec();
}