mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
fixed QR scaner
This commit is contained in:
@@ -214,14 +214,6 @@ if(AMNEZIA_QR_PAIRING_ALLOW_DUPLICATE_VPN_KEY)
|
|||||||
target_compile_definitions(${PROJECT} PRIVATE AMNEZIA_QR_PAIRING_ALLOW_DUPLICATE_VPN_KEY)
|
target_compile_definitions(${PROJECT} PRIVATE AMNEZIA_QR_PAIRING_ALLOW_DUPLICATE_VPN_KEY)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
option(AMNEZIA_LAN_PLAINTEXT_GATEWAY "Dev: plaintext JSON to private LAN gateway hosts (requires AMNEZIA_QR_PAIRING_ALLOW)" OFF)
|
|
||||||
if(AMNEZIA_LAN_PLAINTEXT_GATEWAY)
|
|
||||||
if(NOT AMNEZIA_QR_PAIRING_ALLOW)
|
|
||||||
message(FATAL_ERROR "AMNEZIA_LAN_PLAINTEXT_GATEWAY=ON requires AMNEZIA_QR_PAIRING_ALLOW=ON")
|
|
||||||
endif()
|
|
||||||
target_compile_definitions(${PROJECT} PRIVATE AMNEZIA_LAN_PLAINTEXT_GATEWAY)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
|
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
|
||||||
|
|
||||||
# Finalize the executable so Qt can gather/deploy QML modules and plugins correctly (Android needs this).
|
# Finalize the executable so Qt can gather/deploy QML modules and plugins correctly (Android needs this).
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ bool isLocalGatewayHost(const QString &gatewayUrl)
|
|||||||
|| gatewayUrl.contains(QStringLiteral("::1"), Qt::CaseInsensitive)) {
|
|| gatewayUrl.contains(QStringLiteral("::1"), Qt::CaseInsensitive)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#ifdef AMNEZIA_LAN_PLAINTEXT_GATEWAY
|
#ifdef AMNEZIA_QR_PAIRING_ALLOW
|
||||||
const QUrl u(gatewayUrl);
|
const QUrl u(gatewayUrl);
|
||||||
return NetworkUtilities::hostIsPrivateLanAddress(u.host());
|
return NetworkUtilities::hostIsPrivateLanAddress(u.host());
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#include "subscriptionController.h"
|
#include "subscriptionController.h"
|
||||||
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QEventLoop>
|
#include <QEventLoop>
|
||||||
@@ -339,9 +338,7 @@ ErrorCode SubscriptionController::importServerFromQrPairingResponse(const QStrin
|
|||||||
if (duplicateServerIndex) {
|
if (duplicateServerIndex) {
|
||||||
*duplicateServerIndex = i;
|
*duplicateServerIndex = i;
|
||||||
}
|
}
|
||||||
#ifndef AMNEZIA_QR_PAIRING_ALLOW_DUPLICATE_VPN_KEY
|
|
||||||
return ErrorCode::ApiConfigAlreadyAdded;
|
return ErrorCode::ApiConfigAlreadyAdded;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -387,24 +384,6 @@ ErrorCode SubscriptionController::importServerFromQrPairingResponse(const QStrin
|
|||||||
apiV2->apiConfig.vpnKey = fullKey;
|
apiV2->apiConfig.vpnKey = fullKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef AMNEZIA_QR_PAIRING_ALLOW_DUPLICATE_VPN_KEY
|
|
||||||
static int existingSameKeyCount = 0;
|
|
||||||
if (apiV2) {
|
|
||||||
++existingSameKeyCount;
|
|
||||||
const QString suffix = QCoreApplication::translate("SubscriptionController", " (paired copy %1)")
|
|
||||||
.arg(existingSameKeyCount);
|
|
||||||
|
|
||||||
if (!apiV2->name.isEmpty()) {
|
|
||||||
apiV2->name += suffix;
|
|
||||||
} else if (!apiV2->description.isEmpty()) {
|
|
||||||
apiV2->description += suffix;
|
|
||||||
} else {
|
|
||||||
apiV2->name = QCoreApplication::translate("SubscriptionController", "Paired subscription %1")
|
|
||||||
.arg(existingSameKeyCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
m_serversRepository->addServer(serverConfigModel);
|
m_serversRepository->addServer(serverConfigModel);
|
||||||
serverConfig = serverConfigModel;
|
serverConfig = serverConfigModel;
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
|
|||||||
@@ -58,12 +58,25 @@ namespace
|
|||||||
constexpr QLatin1String unprocessableSubscriptionMessage("Failed to retrieve subscription information. Is it activated?");
|
constexpr QLatin1String unprocessableSubscriptionMessage("Failed to retrieve subscription information. Is it activated?");
|
||||||
|
|
||||||
constexpr int proxyStorageRequestTimeoutMsecs = 3000;
|
constexpr int proxyStorageRequestTimeoutMsecs = 3000;
|
||||||
}
|
|
||||||
|
/** Pairing/subscription paths use "%1api/v1/..." / "%1v1/..." — %1 must end with '/' or the host and path merge (404). */
|
||||||
|
QString normalizedGatewayBase(const QString &endpoint)
|
||||||
|
{
|
||||||
|
QString e = endpoint.trimmed();
|
||||||
|
if (e.isEmpty()) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
if (!e.endsWith(QLatin1Char('/'))) {
|
||||||
|
e.append(QLatin1Char('/'));
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||||
const bool isStrictKillSwitchEnabled, QObject *parent)
|
const bool isStrictKillSwitchEnabled, QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
m_gatewayEndpoint(gatewayEndpoint),
|
m_gatewayEndpoint(normalizedGatewayBase(gatewayEndpoint)),
|
||||||
m_isDevEnvironment(isDevEnvironment),
|
m_isDevEnvironment(isDevEnvironment),
|
||||||
m_requestTimeoutMsecs(requestTimeoutMsecs),
|
m_requestTimeoutMsecs(requestTimeoutMsecs),
|
||||||
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled)
|
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled)
|
||||||
@@ -104,12 +117,10 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
|
|||||||
{
|
{
|
||||||
const QUrl gatewayUrl(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl);
|
const QUrl gatewayUrl(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl);
|
||||||
const QString host = gatewayUrl.host().toLower();
|
const QString host = gatewayUrl.host().toLower();
|
||||||
bool usePlaintext = (host == QLatin1String("localhost") || host == QLatin1String("127.0.0.1") || host == QLatin1String("::1"));
|
const bool loopback =
|
||||||
#ifdef AMNEZIA_LAN_PLAINTEXT_GATEWAY
|
(host == QLatin1String("localhost") || host == QLatin1String("127.0.0.1") || host == QLatin1String("::1"));
|
||||||
if (!usePlaintext) {
|
// tools/local_gateway on a LAN IP (e.g. phone → Mac -auto-public): mock expects plaintext JSON.
|
||||||
usePlaintext = NetworkUtilities::hostIsPrivateLanAddress(host);
|
const bool usePlaintext = loopback || NetworkUtilities::hostIsPrivateLanAddress(host);
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (usePlaintext) {
|
if (usePlaintext) {
|
||||||
encRequestData.isPlaintextLocalGateway = true;
|
encRequestData.isPlaintextLocalGateway = true;
|
||||||
encRequestData.requestBody = QJsonDocument(apiPayload).toJson();
|
encRequestData.requestBody = QJsonDocument(apiPayload).toJson();
|
||||||
|
|||||||
@@ -261,7 +261,7 @@ QString SecureAppSettingsRepository::getGatewayEndpoint(bool isTestPurchase) con
|
|||||||
|| base.contains(QStringLiteral("[::1]"), Qt::CaseInsensitive)) {
|
|| base.contains(QStringLiteral("[::1]"), Qt::CaseInsensitive)) {
|
||||||
return m_gatewayEndpoint;
|
return m_gatewayEndpoint;
|
||||||
}
|
}
|
||||||
#ifdef AMNEZIA_LAN_PLAINTEXT_GATEWAY
|
#ifdef AMNEZIA_QR_PAIRING_ALLOW
|
||||||
{
|
{
|
||||||
const QUrl gatewayUrl(base);
|
const QUrl gatewayUrl(base);
|
||||||
if (NetworkUtilities::hostIsPrivateLanAddress(gatewayUrl.host())) {
|
if (NetworkUtilities::hostIsPrivateLanAddress(gatewayUrl.host())) {
|
||||||
|
|||||||
@@ -221,6 +221,10 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
|
|||||||
return amnezia::ErrorCode::ApiConfigDownloadError;
|
return amnezia::ErrorCode::ApiConfigDownloadError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (httpStatusCode == httpStatusCodeNotFound) {
|
||||||
|
return amnezia::ErrorCode::ApiNotFoundError;
|
||||||
|
}
|
||||||
|
|
||||||
qDebug() << "something went wrong";
|
qDebug() << "something went wrong";
|
||||||
return amnezia::ErrorCode::ApiConfigDownloadError;
|
return amnezia::ErrorCode::ApiConfigDownloadError;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,15 @@
|
|||||||
|
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
QList<QString> qrCodeUtils::generateQrCodeImageSeriesPlainText(const QByteArray &utf8Text)
|
||||||
|
{
|
||||||
|
const QString text = QString::fromUtf8(utf8Text);
|
||||||
|
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(text.toUtf8().constData(), qrcodegen::QrCode::Ecc::LOW);
|
||||||
|
const QString svg = QString::fromStdString(toSvgString(qr, 1));
|
||||||
|
return { svgToBase64(svg) };
|
||||||
|
}
|
||||||
|
|
||||||
QList<QString> qrCodeUtils::generateQrCodeImageSeries(const QByteArray &data)
|
QList<QString> qrCodeUtils::generateQrCodeImageSeries(const QByteArray &data)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ namespace qrCodeUtils
|
|||||||
constexpr const qint16 qrMagicCode = 1984;
|
constexpr const qint16 qrMagicCode = 1984;
|
||||||
|
|
||||||
QList<QString> generateQrCodeImageSeries(const QByteArray &data);
|
QList<QString> generateQrCodeImageSeries(const QByteArray &data);
|
||||||
|
/** QR payload is raw UTF-8 text (e.g. hyphenated session UUID) so phone cameras return a parsable string. */
|
||||||
|
QList<QString> generateQrCodeImageSeriesPlainText(const QByteArray &utf8Text);
|
||||||
qrcodegen::QrCode generateQrCode(const QByteArray &data);
|
qrcodegen::QrCode generateQrCode(const QByteArray &data);
|
||||||
QString svgToBase64(const QString &image);
|
QString svgToBase64(const QString &image);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "pairingUiController.h"
|
#include "pairingUiController.h"
|
||||||
|
|
||||||
|
#include <QDataStream>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QIODevice>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
@@ -40,6 +42,50 @@ int pairingRetryDelayMs(int zeroBasedAttempt)
|
|||||||
constexpr int baseMs = 500;
|
constexpr int baseMs = 500;
|
||||||
return baseMs * (1 << zeroBasedAttempt);
|
return baseMs * (1 << zeroBasedAttempt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Legacy TV QR: generateQrCodeImageSeries base64url-wrapped QDataStream chunk (see qrCodeUtils.cpp). */
|
||||||
|
bool tryDecodeLegacyChunkedPairingQrPayload(const QString &t, QString *outUuid)
|
||||||
|
{
|
||||||
|
static const QRegularExpression binUrlSafe(QStringLiteral("^[A-Za-z0-9_-]+$"));
|
||||||
|
if (!binUrlSafe.match(t).hasMatch() || t.size() < 16) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QByteArray padded = t.toUtf8();
|
||||||
|
switch (padded.size() % 4) {
|
||||||
|
case 2:
|
||||||
|
padded += "==";
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
padded += "=";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const QByteArray raw = QByteArray::fromBase64(padded, QByteArray::Base64UrlEncoding);
|
||||||
|
if (raw.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QDataStream ds(raw);
|
||||||
|
ds.setByteOrder(QDataStream::BigEndian);
|
||||||
|
qint16 magic = 0;
|
||||||
|
quint8 nChunks = 0;
|
||||||
|
quint8 chunkIndex = 0;
|
||||||
|
QByteArray pl;
|
||||||
|
ds >> magic >> nChunks >> chunkIndex >> pl;
|
||||||
|
if (ds.status() != QDataStream::Ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (magic != qrCodeUtils::qrMagicCode || nChunks < 1 || nChunks > 200 || chunkIndex >= nChunks) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const QString candidate = QString::fromUtf8(pl).trimmed();
|
||||||
|
const QUuid u = QUuid::fromString(candidate);
|
||||||
|
if (u.isNull()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*outUuid = u.toString(QUuid::WithoutBraces);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_ANDROID)
|
||||||
@@ -96,17 +142,30 @@ bool PairingUiController::applyScannedTextAsPairingUuid(const QString &raw)
|
|||||||
qInfo() << "[PairingUi] scan rejected: looks like vpn:// bundle, not session UUID";
|
qInfo() << "[PairingUi] scan rejected: looks like vpn:// bundle, not session UUID";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
static const QRegularExpression re(QStringLiteral(
|
static const QRegularExpression reV4(QStringLiteral(
|
||||||
"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}"));
|
"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}"));
|
||||||
const QRegularExpressionMatch m = re.match(t);
|
const QRegularExpressionMatch m = reV4.match(t);
|
||||||
if (!m.hasMatch()) {
|
if (m.hasMatch()) {
|
||||||
qInfo() << "[PairingUi] scan rejected: no UUID v4 pattern in payload";
|
const QString uuid = m.captured(0);
|
||||||
return false;
|
qInfo() << "[PairingUi] scan accepted uuid=" << uuid.left(13) << "...";
|
||||||
|
emit pairingUuidFromScan(uuid);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
const QString uuid = m.captured(0);
|
QString fromLegacy;
|
||||||
qInfo() << "[PairingUi] scan accepted uuid=" << uuid.left(13) << "...";
|
if (tryDecodeLegacyChunkedPairingQrPayload(t, &fromLegacy)) {
|
||||||
emit pairingUuidFromScan(uuid);
|
qInfo() << "[PairingUi] scan accepted legacy chunked QR uuid=" << fromLegacy.left(13) << "...";
|
||||||
return true;
|
emit pairingUuidFromScan(fromLegacy);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const QUuid parsed = QUuid::fromString(t);
|
||||||
|
if (!parsed.isNull()) {
|
||||||
|
const QString canon = parsed.toString(QUuid::WithoutBraces);
|
||||||
|
qInfo() << "[PairingUi] scan accepted QUuid::fromString uuid=" << canon.left(13) << "...";
|
||||||
|
emit pairingUuidFromScan(canon);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
qInfo() << "[PairingUi] scan rejected: no session UUID recognized in payload";
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_ANDROID)
|
||||||
@@ -201,6 +260,8 @@ QString PairingUiController::tvFailureMessage(ErrorCode code) const
|
|||||||
return tr("QR session expired. Tap Start to show a new QR code.");
|
return tr("QR session expired. Tap Start to show a new QR code.");
|
||||||
case ErrorCode::ApiConfigAlreadyAdded:
|
case ErrorCode::ApiConfigAlreadyAdded:
|
||||||
return tr("This configuration is already on the device.");
|
return tr("This configuration is already on the device.");
|
||||||
|
case ErrorCode::ApiNotFoundError:
|
||||||
|
return tr("This gateway does not expose QR pairing (HTTP 404). Check the gateway URL or use the local mock (tools/local_gateway).");
|
||||||
default:
|
default:
|
||||||
return tr("Pairing failed");
|
return tr("Pairing failed");
|
||||||
}
|
}
|
||||||
@@ -226,7 +287,7 @@ void PairingUiController::startTvQrSession()
|
|||||||
|
|
||||||
m_tvSessionUuid = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
m_tvSessionUuid = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||||
const QByteArray qrPayload = m_tvSessionUuid.toUtf8();
|
const QByteArray qrPayload = m_tvSessionUuid.toUtf8();
|
||||||
m_tvQrCodes = qrCodeUtils::generateQrCodeImageSeries(qrPayload);
|
m_tvQrCodes = qrCodeUtils::generateQrCodeImageSeriesPlainText(qrPayload);
|
||||||
emit tvQrCodesChanged();
|
emit tvQrCodesChanged();
|
||||||
emit tvSessionUuidChanged();
|
emit tvSessionUuidChanged();
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,17 @@ PageType {
|
|||||||
|
|
||||||
property int qrImageIndex: 0
|
property int qrImageIndex: 0
|
||||||
property bool pairingCameraOpen: false
|
property bool pairingCameraOpen: false
|
||||||
|
property int lastPairingScanToastClockMs: 0
|
||||||
|
|
||||||
|
function notifyPairingScanSuccess() {
|
||||||
|
const now = new Date().getTime()
|
||||||
|
if (now - root.lastPairingScanToastClockMs < 1600) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
root.lastPairingScanToastClockMs = now
|
||||||
|
PageController.showNotificationMessage(
|
||||||
|
qsTr("QR session ID captured. Tap Send from current subscription to complete pairing."))
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: pairingCameraKickTimer
|
id: pairingCameraKickTimer
|
||||||
@@ -238,13 +249,12 @@ PageType {
|
|||||||
|
|
||||||
QRCodeReader {
|
QRCodeReader {
|
||||||
id: pairingQrReader
|
id: pairingQrReader
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
onCodeReaded: function(code) {
|
onCodeReaded: function(code) {
|
||||||
if (PairingUiController.applyScannedTextAsPairingUuid(code)) {
|
if (PairingUiController.applyScannedTextAsPairingUuid(code)) {
|
||||||
pairingQrReader.stopReading()
|
pairingQrReader.stopReading()
|
||||||
root.pairingCameraOpen = false
|
root.pairingCameraOpen = false
|
||||||
PageController.showNotificationMessage(qsTr("Session ID filled from QR"))
|
root.notifyPairingScanSuccess()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,18 @@ PageType {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool pairingCameraOpen: false
|
property bool pairingCameraOpen: false
|
||||||
|
/** iOS AVFoundation can fire the same QR repeatedly; avoid stacking identical toasts. */
|
||||||
|
property int lastPairingScanToastClockMs: 0
|
||||||
|
|
||||||
|
function notifyPairingScanSuccess() {
|
||||||
|
const now = new Date().getTime()
|
||||||
|
if (now - root.lastPairingScanToastClockMs < 1600) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
root.lastPairingScanToastClockMs = now
|
||||||
|
PageController.showNotificationMessage(
|
||||||
|
qsTr("QR session ID captured. Tap Send from current subscription to complete pairing."))
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: pairingCameraKickTimer
|
id: pairingCameraKickTimer
|
||||||
@@ -160,13 +172,12 @@ PageType {
|
|||||||
|
|
||||||
QRCodeReader {
|
QRCodeReader {
|
||||||
id: pairingQrReader
|
id: pairingQrReader
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
onCodeReaded: function(code) {
|
onCodeReaded: function(code) {
|
||||||
if (PairingUiController.applyScannedTextAsPairingUuid(code)) {
|
if (PairingUiController.applyScannedTextAsPairingUuid(code)) {
|
||||||
pairingQrReader.stopReading()
|
pairingQrReader.stopReading()
|
||||||
root.pairingCameraOpen = false
|
root.pairingCameraOpen = false
|
||||||
PageController.showNotificationMessage(qsTr("Session ID filled from QR"))
|
root.notifyPairingScanSuccess()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user