File indexing completed on 2024-06-02 05:44:06

0001 /*
0002     Copyright 2020  Devin Lin <espidev@gmail.com>
0003 
0004     This library is free software; you can redistribute it and/or
0005     modify it under the terms of the GNU Lesser General Public
0006     License as published by the Free Software Foundation; either
0007     version 2.1 of the License, or (at your option) version 3, or any
0008     later version accepted by the membership of KDE e.V. (or its
0009     successor approved by the membership of KDE e.V.), which shall
0010     act as a proxy defined in Section 6 of version 3 of the license.
0011 
0012     This library is distributed in the hope that it will be useful,
0013     but WITHOUT ANY WARRANTY; without even the implied warranty of
0014     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015     Lesser General Public License for more details.
0016 
0017     You should have received a copy of the GNU Lesser General Public
0018     License along with this library.  If not, see <http://www.gnu.org/licenses/>.
0019 */
0020 
0021 #include "fingerprintmodel.h"
0022 #include "fprintdevice.h"
0023 
0024 FingerprintModel::FingerprintModel(QObject *parent)
0025     : QObject(parent)
0026     , m_managerDbusInterface(new NetReactivatedFprintManagerInterface(QStringLiteral("net.reactivated.Fprint"),
0027                                                                       QStringLiteral("/net/reactivated/Fprint/Manager"),
0028                                                                       QDBusConnection::systemBus(),
0029                                                                       this))
0030 {
0031     auto reply = m_managerDbusInterface->GetDefaultDevice();
0032     reply.waitForFinished();
0033 
0034     if (reply.isError()) {
0035         qDebug() << reply.error().message();
0036         setCurrentError(reply.error().message());
0037         return;
0038     }
0039 
0040     QDBusObjectPath path = reply.value();
0041     m_device = new FprintDevice(path, this);
0042 
0043     connect(m_device, &FprintDevice::enrollCompleted, this, &FingerprintModel::handleEnrollCompleted);
0044     connect(m_device, &FprintDevice::enrollStagePassed, this, &FingerprintModel::handleEnrollStagePassed);
0045     connect(m_device, &FprintDevice::enrollRetryStage, this, &FingerprintModel::handleEnrollRetryStage);
0046     connect(m_device, &FprintDevice::enrollFailed, this, &FingerprintModel::handleEnrollFailed);
0047 }
0048 
0049 FingerprintModel::~FingerprintModel()
0050 {
0051     if (m_device) { // just in case device is claimed
0052         m_device->stopEnrolling();
0053         m_device->release();
0054     }
0055 }
0056 
0057 FprintDevice::ScanType FingerprintModel::scanType()
0058 {
0059     return !m_device ? FprintDevice::Press : m_device->scanType();
0060 }
0061 
0062 QString FingerprintModel::currentError()
0063 {
0064     return m_currentError;
0065 }
0066 
0067 void FingerprintModel::setCurrentError(QString error)
0068 {
0069     if (m_currentError != error) {
0070         m_currentError = error;
0071         Q_EMIT currentErrorChanged();
0072     }
0073 }
0074 
0075 QString FingerprintModel::enrollFeedback()
0076 {
0077     return m_enrollFeedback;
0078 }
0079 
0080 void FingerprintModel::setEnrollFeedback(QString feedback)
0081 {
0082     m_enrollFeedback = feedback;
0083     Q_EMIT enrollFeedbackChanged();
0084 }
0085 
0086 bool FingerprintModel::currentlyEnrolling()
0087 {
0088     return m_currentlyEnrolling;
0089 }
0090 
0091 bool FingerprintModel::deviceFound()
0092 {
0093     return m_device != nullptr;
0094 }
0095 
0096 double FingerprintModel::enrollProgress()
0097 {
0098     if (!deviceFound()) {
0099         return 0;
0100     }
0101     return (m_device->numOfEnrollStages() == 0) ? 1 : ((double)m_enrollStage) / m_device->numOfEnrollStages();
0102 }
0103 
0104 void FingerprintModel::setEnrollStage(int stage)
0105 {
0106     m_enrollStage = stage;
0107     Q_EMIT enrollProgressChanged();
0108 }
0109 
0110 FingerprintModel::DialogState FingerprintModel::dialogState()
0111 {
0112     return m_dialogState;
0113 }
0114 
0115 void FingerprintModel::setDialogState(DialogState dialogState)
0116 {
0117     m_dialogState = dialogState;
0118     Q_EMIT dialogStateChanged();
0119 }
0120 
0121 void FingerprintModel::switchUser(QString username)
0122 {
0123     m_username = username;
0124 
0125     if (deviceFound()) {
0126         stopEnrolling(); // stop enrolling if ongoing
0127         m_device->release(); // release from old user
0128 
0129         Q_EMIT enrolledFingerprintsChanged();
0130     }
0131 }
0132 
0133 bool FingerprintModel::claimDevice()
0134 {
0135     if (!deviceFound()) {
0136         return false;
0137     }
0138 
0139     QDBusError error = m_device->claim(m_username);
0140     if (error.isValid() && error.name() != "net.reactivated.Fprint.Error.AlreadyInUse") {
0141         qDebug() << "error claiming:" << error.message();
0142         setCurrentError(error.message());
0143         return false;
0144     }
0145     return true;
0146 }
0147 
0148 void FingerprintModel::startEnrolling(QString finger)
0149 {
0150     if (!deviceFound()) {
0151         setCurrentError(i18n("No fingerprint device found."));
0152         setDialogState(DialogState::FingerprintList);
0153         return;
0154     }
0155 
0156     setEnrollStage(0);
0157     setEnrollFeedback({});
0158 
0159     // claim device for user
0160     if (!claimDevice()) {
0161         setDialogState(DialogState::FingerprintList);
0162         return;
0163     }
0164 
0165     QDBusError error = m_device->startEnrolling(finger);
0166     if (error.isValid()) {
0167         qDebug() << "error start enrolling:" << error.message();
0168         setCurrentError(error.message());
0169         m_device->release();
0170         setDialogState(DialogState::FingerprintList);
0171         return;
0172     }
0173 
0174     m_currentlyEnrolling = true;
0175     Q_EMIT currentlyEnrollingChanged();
0176 
0177     setDialogState(DialogState::Enrolling);
0178 }
0179 
0180 void FingerprintModel::stopEnrolling()
0181 {
0182     setDialogState(DialogState::FingerprintList);
0183     if (m_currentlyEnrolling) {
0184         m_currentlyEnrolling = false;
0185         Q_EMIT currentlyEnrollingChanged();
0186 
0187         QDBusError error = m_device->stopEnrolling();
0188         if (error.isValid()) {
0189             qDebug() << "error stop enrolling:" << error.message();
0190             setCurrentError(error.message());
0191             return;
0192         }
0193         m_device->release();
0194     }
0195 }
0196 
0197 void FingerprintModel::deleteFingerprint(QString finger)
0198 {
0199     // claim for user
0200     if (!claimDevice()) {
0201         return;
0202     }
0203 
0204     QDBusError error = m_device->deleteEnrolledFinger(finger);
0205     if (error.isValid()) {
0206         qDebug() << "error deleting fingerprint:" << error.message();
0207         setCurrentError(error.message());
0208     }
0209 
0210     // release from user
0211     error = m_device->release();
0212     if (error.isValid()) {
0213         qDebug() << "error releasing:" << error.message();
0214         setCurrentError(error.message());
0215     }
0216 
0217     Q_EMIT enrolledFingerprintsChanged();
0218 }
0219 
0220 void FingerprintModel::clearFingerprints()
0221 {
0222     // claim for user
0223     if (!claimDevice()) {
0224         return;
0225     }
0226 
0227     QDBusError error = m_device->deleteEnrolledFingers();
0228     if (error.isValid()) {
0229         qDebug() << "error clearing fingerprints:" << error.message();
0230         setCurrentError(error.message());
0231     }
0232 
0233     // release from user
0234     error = m_device->release();
0235     if (error.isValid()) {
0236         qDebug() << "error releasing:" << error.message();
0237         setCurrentError(error.message());
0238     }
0239 
0240     Q_EMIT enrolledFingerprintsChanged();
0241 }
0242 
0243 QStringList FingerprintModel::enrolledFingerprintsRaw()
0244 {
0245     if (deviceFound()) {
0246         QDBusPendingReply<QStringList> reply = m_device->listEnrolledFingers(m_username);
0247         reply.waitForFinished();
0248         if (reply.isError()) {
0249             // ignore net.reactivated.Fprint.Error.NoEnrolledPrints, as it shows up when there are no fingerprints
0250             if (reply.error().name() != "net.reactivated.Fprint.Error.NoEnrolledPrints") {
0251                 qDebug() << "error listing enrolled fingers:" << reply.error().message();
0252                 setCurrentError(reply.error().message());
0253             }
0254             return QStringList();
0255         }
0256         return reply.value();
0257     } else {
0258         setCurrentError(i18n("No fingerprint device found."));
0259         setDialogState(DialogState::FingerprintList);
0260         return QStringList();
0261     }
0262 }
0263 
0264 QVariantList FingerprintModel::enrolledFingerprints()
0265 {
0266     // convert fingers list to qlist of Finger objects
0267     QVariantList fingers;
0268     for (QString &finger : enrolledFingerprintsRaw()) {
0269         for (Finger *storedFinger : FINGERS) {
0270             if (storedFinger->internalName() == finger) {
0271                 fingers.append(QVariant::fromValue(storedFinger));
0272                 break;
0273             }
0274         }
0275     }
0276     return fingers;
0277 }
0278 
0279 QVariantList FingerprintModel::availableFingersToEnroll()
0280 {
0281     QVariantList list;
0282     QStringList enrolled = enrolledFingerprintsRaw();
0283 
0284     // add fingerprints to list that are not in the enrolled list
0285     for (Finger *finger : FINGERS) {
0286         if (!enrolledFingerprintsRaw().contains(finger->internalName())) {
0287             list.append(QVariant::fromValue(finger));
0288         }
0289     }
0290     return list;
0291 }
0292 
0293 QVariantList FingerprintModel::unavailableFingersToEnroll()
0294 {
0295     QVariantList list;
0296     QStringList enrolled = enrolledFingerprintsRaw();
0297 
0298     // add fingerprints to list that are in the enrolled list
0299     for (Finger *finger : FINGERS) {
0300         if (enrolledFingerprintsRaw().contains(finger->internalName())) {
0301             list.append(QVariant::fromValue(finger));
0302         }
0303     }
0304     return list;
0305 }
0306 
0307 void FingerprintModel::handleEnrollCompleted()
0308 {
0309     setEnrollStage(m_device->numOfEnrollStages());
0310     setEnrollFeedback({});
0311     Q_EMIT enrolledFingerprintsChanged();
0312     Q_EMIT scanComplete();
0313 
0314     // stopEnrolling not called, as it is triggered only when the "complete" button is pressed
0315     // (only change dialog state change after button is pressed)
0316     setDialogState(DialogState::EnrollComplete);
0317 }
0318 
0319 void FingerprintModel::handleEnrollStagePassed()
0320 {
0321     setEnrollStage(m_enrollStage + 1);
0322     setEnrollFeedback({});
0323     Q_EMIT scanSuccess();
0324     qDebug() << "fingerprint enroll stage pass:" << enrollProgress();
0325 }
0326 
0327 void FingerprintModel::handleEnrollRetryStage(QString feedback)
0328 {
0329     Q_EMIT scanFailure();
0330     if (feedback == "enroll-retry-scan") {
0331         setEnrollFeedback(i18n("Retry scanning your finger."));
0332     } else if (feedback == "enroll-swipe-too-short") {
0333         setEnrollFeedback(i18n("Swipe too short. Try again."));
0334     } else if (feedback == "enroll-finger-not-centered") {
0335         setEnrollFeedback(i18n("Finger not centered on the reader. Try again."));
0336     } else if (feedback == "enroll-remove-and-retry") {
0337         setEnrollFeedback(i18n("Remove your finger from the reader, and try again."));
0338     }
0339     qDebug() << "fingerprint enroll stage fail:" << feedback;
0340 }
0341 
0342 void FingerprintModel::handleEnrollFailed(QString error)
0343 {
0344     if (error == "enroll-failed") {
0345         setCurrentError(i18n("Fingerprint enrollment has failed."));
0346         stopEnrolling();
0347     } else if (error == "enroll-data-full") {
0348         setCurrentError(i18n("There is no space left for this device, delete other fingerprints to continue."));
0349         stopEnrolling();
0350     } else if (error == "enroll-disconnected") {
0351         setCurrentError(i18n("The device was disconnected."));
0352         m_currentlyEnrolling = false;
0353         Q_EMIT currentlyEnrollingChanged();
0354         setDialogState(DialogState::FingerprintList);
0355     } else if (error == "enroll-unknown-error") {
0356         setCurrentError(i18n("An unknown error has occurred."));
0357         stopEnrolling();
0358     }
0359 }