mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
fixed iOS QRCodeReader
This commit is contained in:
@@ -33,6 +33,16 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
void execNetworkWaitLoop(QEventLoop &wait)
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
// QEventLoop::ExcludeUserInputEvents is not supported on iOS (Qt warns; can break nested UI).
|
||||
wait.exec();
|
||||
#else
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
#endif
|
||||
}
|
||||
|
||||
constexpr QLatin1String errorResponsePattern1("No active configuration found for");
|
||||
constexpr QLatin1String errorResponsePattern2("No non-revoked public key found for");
|
||||
constexpr QLatin1String errorResponsePattern3("Account not found.");
|
||||
@@ -189,7 +199,7 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
||||
|
||||
QList<QSslError> sslErrors;
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
execNetworkWaitLoop(wait);
|
||||
|
||||
QByteArray encryptedResponseBody = reply->readAll();
|
||||
QString replyErrorString = reply->errorString();
|
||||
@@ -431,7 +441,7 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
||||
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
execNetworkWaitLoop(wait);
|
||||
|
||||
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||
auto encryptedResponseBody = reply->readAll();
|
||||
@@ -564,7 +574,7 @@ void GatewayController::bypassProxy(const QString &endpoint, const QString &serv
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
execNetworkWaitLoop(wait);
|
||||
|
||||
auto result = replyProcessingFunction(reply, sslErrors);
|
||||
reply->deleteLater();
|
||||
@@ -586,7 +596,7 @@ void GatewayController::bypassProxy(const QString &endpoint, const QString &serv
|
||||
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
execNetworkWaitLoop(wait);
|
||||
|
||||
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||
reply->deleteLater();
|
||||
|
||||
@@ -12,3 +12,4 @@ QRect QRCodeReader::cameraSize() {
|
||||
void QRCodeReader::startReading() {}
|
||||
void QRCodeReader::stopReading() {}
|
||||
void QRCodeReader::setCameraSize(QRect) {}
|
||||
void QRCodeReader::notifyCodeRead(const QString &) {}
|
||||
|
||||
@@ -16,6 +16,8 @@ public slots:
|
||||
void startReading();
|
||||
void stopReading();
|
||||
void setCameraSize(QRect value);
|
||||
/// Called from AVFoundation delegate on the main queue; emits codeReaded.
|
||||
void notifyCodeRead(const QString &code);
|
||||
|
||||
signals:
|
||||
void codeReaded(QString code);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#if !MACOS_NE
|
||||
#include "QRCodeReaderBase.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDebug>
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <AVFoundation/AVFoundation.h>
|
||||
|
||||
@@ -27,13 +30,15 @@
|
||||
}
|
||||
|
||||
- (BOOL)startReading {
|
||||
NSError *error;
|
||||
[self stopReading];
|
||||
|
||||
NSError *error = nil;
|
||||
|
||||
AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType: AVMediaTypeVideo];
|
||||
AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice: captureDevice error: &error];
|
||||
|
||||
if(!deviceInput) {
|
||||
NSLog(@"Error %@", error.localizedDescription);
|
||||
if (!deviceInput) {
|
||||
NSLog(@"[QRCodeReader] deviceInput failed: %@", error.localizedDescription);
|
||||
return NO;
|
||||
}
|
||||
|
||||
@@ -66,14 +71,21 @@
|
||||
|
||||
[_captureSession startRunning];
|
||||
|
||||
NSLog(@"[QRCodeReader] startReading OK frame=(%.1f,%.1f,%.1f,%.1f) statusBar=%.1f",
|
||||
cameraCGRect.origin.x, cameraCGRect.origin.y, cameraCGRect.size.width, cameraCGRect.size.height, statusBarHeight);
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)stopReading {
|
||||
[_captureSession stopRunning];
|
||||
_captureSession = nil;
|
||||
|
||||
[_videoPreviewPlayer removeFromSuperlayer];
|
||||
if (_captureSession) {
|
||||
[_captureSession stopRunning];
|
||||
_captureSession = nil;
|
||||
}
|
||||
if (_videoPreviewPlayer) {
|
||||
[_videoPreviewPlayer removeFromSuperlayer];
|
||||
_videoPreviewPlayer = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
|
||||
@@ -82,7 +94,16 @@
|
||||
AVMetadataMachineReadableCodeObject *metadataObject = [metadataObjects objectAtIndex:0];
|
||||
|
||||
if ([[metadataObject type] isEqualToString: AVMetadataObjectTypeQRCode]) {
|
||||
_qrCodeReader->emit codeReaded([metadataObject stringValue].UTF8String);
|
||||
NSString *value = [metadataObject stringValue];
|
||||
if (value.length == 0) {
|
||||
return;
|
||||
}
|
||||
NSLog(@"[QRCodeReader] metadata QR len=%lu", static_cast<unsigned long>(value.length));
|
||||
QRCodeReader *cpp = _qrCodeReader;
|
||||
const QByteArray utf8([value UTF8String]);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
cpp->notifyCodeRead(QString::fromUtf8(utf8));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,14 +121,23 @@ QRect QRCodeReader::cameraSize() {
|
||||
|
||||
void QRCodeReader::setCameraSize(QRect value) {
|
||||
m_cameraSize = value;
|
||||
qInfo() << "[QRCodeReader] setCameraSize" << value;
|
||||
}
|
||||
|
||||
void QRCodeReader::startReading() {
|
||||
[m_qrCodeReader startReading];
|
||||
const BOOL ok = [m_qrCodeReader startReading];
|
||||
if (!ok) {
|
||||
qWarning() << "[QRCodeReader] startReading failed (see NSLogs)";
|
||||
}
|
||||
}
|
||||
|
||||
void QRCodeReader::stopReading() {
|
||||
[m_qrCodeReader stopReading];
|
||||
qInfo() << "[QRCodeReader] stopReading";
|
||||
}
|
||||
|
||||
void QRCodeReader::notifyCodeRead(const QString &code) {
|
||||
emit codeReaded(code);
|
||||
}
|
||||
#else
|
||||
#include "QRCodeReaderBase.h"
|
||||
@@ -124,4 +154,5 @@ QRect QRCodeReader::cameraSize() {
|
||||
void QRCodeReader::startReading() {}
|
||||
void QRCodeReader::stopReading() {}
|
||||
void QRCodeReader::setCameraSize(QRect) {}
|
||||
void QRCodeReader::notifyCodeRead(const QString &) {}
|
||||
#endif
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "pairingUiController.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QRegularExpression>
|
||||
#include <QTimer>
|
||||
#include <QUuid>
|
||||
@@ -82,6 +83,7 @@ void PairingUiController::setTvPairingUiPhase(int phase)
|
||||
void PairingUiController::openPairingQrScanner()
|
||||
{
|
||||
#if defined(Q_OS_ANDROID)
|
||||
qInfo() << "[PairingUi] openPairingQrScanner (Android native activity)";
|
||||
AndroidController::instance()->startQrReaderActivity();
|
||||
#endif
|
||||
}
|
||||
@@ -89,16 +91,20 @@ void PairingUiController::openPairingQrScanner()
|
||||
bool PairingUiController::applyScannedTextAsPairingUuid(const QString &raw)
|
||||
{
|
||||
const QString t = raw.trimmed();
|
||||
qInfo() << "[PairingUi] scan raw len=" << t.size();
|
||||
if (t.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) {
|
||||
qInfo() << "[PairingUi] scan rejected: looks like vpn:// bundle, not session UUID";
|
||||
return false;
|
||||
}
|
||||
static const QRegularExpression re(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}"));
|
||||
const QRegularExpressionMatch m = re.match(t);
|
||||
if (!m.hasMatch()) {
|
||||
qInfo() << "[PairingUi] scan rejected: no UUID v4 pattern in payload";
|
||||
return false;
|
||||
}
|
||||
const QString uuid = m.captured(0);
|
||||
qInfo() << "[PairingUi] scan accepted uuid=" << uuid.left(13) << "...";
|
||||
emit pairingUuidFromScan(uuid);
|
||||
return true;
|
||||
}
|
||||
@@ -367,24 +373,29 @@ void PairingUiController::submitPhonePairing(const QString &qrUuid, int serverIn
|
||||
}
|
||||
|
||||
const QString trimmedUuid = qrUuid.trimmed();
|
||||
qInfo() << "[PairingUi] submitPhonePairing serverIndex=" << serverIndex << "uuidLen=" << trimmedUuid.size();
|
||||
if (trimmedUuid.isEmpty()) {
|
||||
qWarning() << "[PairingUi] submitPhonePairing aborted: empty UUID (paste or scan first)";
|
||||
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (serverIndex < 0 || serverIndex >= m_serversController->getServersCount()) {
|
||||
qWarning() << "[PairingUi] submitPhonePairing invalid serverIndex";
|
||||
emit errorOccurred(ErrorCode::InternalError);
|
||||
return;
|
||||
}
|
||||
|
||||
const ServerConfig serverConfig = m_serversController->getServerConfig(serverIndex);
|
||||
if (!serverConfig.isApiV2()) {
|
||||
qWarning() << "[PairingUi] submitPhonePairing server is not API v2";
|
||||
emit errorOccurred(ErrorCode::InternalError);
|
||||
return;
|
||||
}
|
||||
|
||||
const ApiV2ServerConfig *apiV2 = serverConfig.as<ApiV2ServerConfig>();
|
||||
if (!apiV2) {
|
||||
qWarning() << "[PairingUi] submitPhonePairing cast to ApiV2ServerConfig failed";
|
||||
emit errorOccurred(ErrorCode::InternalError);
|
||||
return;
|
||||
}
|
||||
@@ -392,6 +403,7 @@ void PairingUiController::submitPhonePairing(const QString &qrUuid, int serverIn
|
||||
QString vpnKey;
|
||||
const ErrorCode keyErr = m_subscriptionController->prepareVpnKeyExport(serverIndex, vpnKey);
|
||||
if (keyErr != ErrorCode::NoError) {
|
||||
qWarning() << "[PairingUi] prepareVpnKeyExport failed" << static_cast<int>(keyErr);
|
||||
emit errorOccurred(keyErr);
|
||||
return;
|
||||
}
|
||||
@@ -400,6 +412,7 @@ void PairingUiController::submitPhonePairing(const QString &qrUuid, int serverIn
|
||||
const QJsonArray supportedProtocols = apiV2->apiConfig.supportedProtocols;
|
||||
const QString apiKey = apiV2->authData.apiKey;
|
||||
if (apiKey.isEmpty()) {
|
||||
qWarning() << "[PairingUi] submitPhonePairing aborted: empty API key on server card";
|
||||
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -435,7 +435,11 @@ bool SubscriptionUiController::getAccountInfo(int serverIndex, bool reload)
|
||||
if (reload) {
|
||||
QEventLoop wait;
|
||||
QTimer::singleShot(1000, &wait, &QEventLoop::quit);
|
||||
#ifdef Q_OS_IOS
|
||||
wait.exec();
|
||||
#else
|
||||
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||
#endif
|
||||
}
|
||||
QJsonObject accountInfo;
|
||||
ErrorCode errorCode = m_subscriptionController->getAccountInfo(serverIndex, accountInfo);
|
||||
|
||||
@@ -16,10 +16,34 @@ PageType {
|
||||
property int qrImageIndex: 0
|
||||
property bool pairingCameraOpen: false
|
||||
|
||||
Timer {
|
||||
id: pairingCameraKickTimer
|
||||
interval: 180
|
||||
repeat: false
|
||||
onTriggered: root.restartPairingIosCamera()
|
||||
}
|
||||
|
||||
function restartPairingIosCamera() {
|
||||
if (Qt.platform.os !== "ios" || !root.pairingCameraOpen) {
|
||||
return
|
||||
}
|
||||
if (cameraSlot.width < 32 || cameraSlot.height < 32) {
|
||||
console.info("[PairingQr] cameraSlot too small wxh=", cameraSlot.width, cameraSlot.height, "retry")
|
||||
pairingCameraKickTimer.restart()
|
||||
return
|
||||
}
|
||||
var p = cameraSlot.mapToItem(root, 0, 0)
|
||||
console.info("[PairingQr] start preview frame", p.x, p.y, cameraSlot.width, cameraSlot.height)
|
||||
pairingQrReader.stopReading()
|
||||
pairingQrReader.setCameraSize(Qt.rect(Math.round(p.x), Math.round(p.y), Math.round(cameraSlot.width), Math.round(cameraSlot.height)))
|
||||
pairingQrReader.startReading()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onVisibleChanged() {
|
||||
if (!root.visible) {
|
||||
pairingCameraKickTimer.stop()
|
||||
pairingQrReader.stopReading()
|
||||
root.pairingCameraOpen = false
|
||||
PairingUiController.cancelAllPairingActivity()
|
||||
@@ -27,6 +51,31 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onPairingCameraOpenChanged() {
|
||||
if (!root.pairingCameraOpen) {
|
||||
pairingCameraKickTimer.stop()
|
||||
pairingQrReader.stopReading()
|
||||
return
|
||||
}
|
||||
if (Qt.platform.os === "ios") {
|
||||
pairingCameraKickTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: cameraSlot
|
||||
enabled: Qt.platform.os === "ios" && root.pairingCameraOpen
|
||||
function onWidthChanged() {
|
||||
pairingCameraKickTimer.restart()
|
||||
}
|
||||
function onHeightChanged() {
|
||||
pairingCameraKickTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
anchors.fill: parent
|
||||
contentHeight: layout.implicitHeight
|
||||
@@ -189,6 +238,7 @@ PageType {
|
||||
|
||||
QRCodeReader {
|
||||
id: pairingQrReader
|
||||
anchors.fill: parent
|
||||
|
||||
onCodeReaded: function(code) {
|
||||
if (PairingUiController.applyScannedTextAsPairingUuid(code)) {
|
||||
@@ -205,11 +255,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
if (Qt.platform.os === "ios") {
|
||||
Qt.callLater(function() {
|
||||
var p = cameraSlot.mapToItem(root, 0, 0)
|
||||
pairingQrReader.setCameraSize(Qt.rect(p.x, p.y, cameraSlot.width, cameraSlot.height))
|
||||
pairingQrReader.startReading()
|
||||
})
|
||||
pairingCameraKickTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,34 @@ PageType {
|
||||
|
||||
property bool pairingCameraOpen: false
|
||||
|
||||
Timer {
|
||||
id: pairingCameraKickTimer
|
||||
interval: 180
|
||||
repeat: false
|
||||
onTriggered: root.restartPairingIosCamera()
|
||||
}
|
||||
|
||||
function restartPairingIosCamera() {
|
||||
if (Qt.platform.os !== "ios" || !root.pairingCameraOpen) {
|
||||
return
|
||||
}
|
||||
if (cameraSlot.width < 32 || cameraSlot.height < 32) {
|
||||
console.info("[PairingQr] cameraSlot too small wxh=", cameraSlot.width, cameraSlot.height, "retry")
|
||||
pairingCameraKickTimer.restart()
|
||||
return
|
||||
}
|
||||
var p = cameraSlot.mapToItem(root, 0, 0)
|
||||
console.info("[PairingQr] start preview frame", p.x, p.y, cameraSlot.width, cameraSlot.height)
|
||||
pairingQrReader.stopReading()
|
||||
pairingQrReader.setCameraSize(Qt.rect(Math.round(p.x), Math.round(p.y), Math.round(cameraSlot.width), Math.round(cameraSlot.height)))
|
||||
pairingQrReader.startReading()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onVisibleChanged() {
|
||||
if (!root.visible) {
|
||||
pairingCameraKickTimer.stop()
|
||||
pairingQrReader.stopReading()
|
||||
root.pairingCameraOpen = false
|
||||
PairingUiController.cancelAllPairingActivity()
|
||||
@@ -26,6 +50,31 @@ PageType {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root
|
||||
function onPairingCameraOpenChanged() {
|
||||
if (!root.pairingCameraOpen) {
|
||||
pairingCameraKickTimer.stop()
|
||||
pairingQrReader.stopReading()
|
||||
return
|
||||
}
|
||||
if (Qt.platform.os === "ios") {
|
||||
pairingCameraKickTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: cameraSlot
|
||||
enabled: Qt.platform.os === "ios" && root.pairingCameraOpen
|
||||
function onWidthChanged() {
|
||||
pairingCameraKickTimer.restart()
|
||||
}
|
||||
function onHeightChanged() {
|
||||
pairingCameraKickTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
FlickableType {
|
||||
anchors.fill: parent
|
||||
contentHeight: layout.implicitHeight
|
||||
@@ -111,6 +160,7 @@ PageType {
|
||||
|
||||
QRCodeReader {
|
||||
id: pairingQrReader
|
||||
anchors.fill: parent
|
||||
|
||||
onCodeReaded: function(code) {
|
||||
if (PairingUiController.applyScannedTextAsPairingUuid(code)) {
|
||||
@@ -127,11 +177,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
if (Qt.platform.os === "ios") {
|
||||
Qt.callLater(function() {
|
||||
var p = cameraSlot.mapToItem(root, 0, 0)
|
||||
pairingQrReader.setCameraSize(Qt.rect(p.x, p.y, cameraSlot.width, cameraSlot.height))
|
||||
pairingQrReader.startReading()
|
||||
})
|
||||
pairingCameraKickTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user