remove log & remove temp code

This commit is contained in:
dranik
2026-05-19 23:11:34 +03:00
parent d4833454ef
commit e554e9b8b4
21 changed files with 34 additions and 1374 deletions
@@ -108,7 +108,6 @@ class AmneziaActivity : QtActivity(), LifecycleOwner {
private var pendingOpenFileUri: String? = null private var pendingOpenFileUri: String? = null
private var openFileDeliveryScheduled = false private var openFileDeliveryScheduled = false
private var pairingQrEmbeddedCamera: PairingQrEmbeddedCamera? = null
private var lastPairingQrReaderStartUptimeMs: Long = 0L private var lastPairingQrReaderStartUptimeMs: Long = 0L
private val vpnServiceEventHandler: Handler by lazy(NONE) { private val vpnServiceEventHandler: Handler by lazy(NONE) {
@@ -998,26 +997,6 @@ class AmneziaActivity : QtActivity(), LifecycleOwner {
return heightDp return heightDp
} }
@Suppress("unused")
fun startPairingQrEmbeddedCamera() {
Log.v(TAG, "startPairingQrEmbeddedCamera")
if (pairingQrEmbeddedCamera == null) {
pairingQrEmbeddedCamera = PairingQrEmbeddedCamera(this)
}
pairingQrEmbeddedCamera?.start()
}
@Suppress("unused")
fun stopPairingQrEmbeddedCamera() {
Log.v(TAG, "stopPairingQrEmbeddedCamera")
pairingQrEmbeddedCamera?.stop()
}
@Suppress("unused")
fun setPairingQrEmbeddedTorch(enabled: Boolean) {
pairingQrEmbeddedCamera?.setTorch(enabled)
}
@Suppress("unused") @Suppress("unused")
fun startQrCodeReader() { fun startQrCodeReader() {
Log.v(TAG, "Start camera") Log.v(TAG, "Start camera")
@@ -1030,11 +1009,9 @@ class AmneziaActivity : QtActivity(), LifecycleOwner {
fun startPairingQrCodeReader() { fun startPairingQrCodeReader() {
val now = SystemClock.uptimeMillis() val now = SystemClock.uptimeMillis()
if (now - lastPairingQrReaderStartUptimeMs < 1200L) { if (now - lastPairingQrReaderStartUptimeMs < 1200L) {
Log.w(TAG, "startPairingQrCodeReader: suppressed duplicate (${now - lastPairingQrReaderStartUptimeMs}ms)")
return return
} }
lastPairingQrReaderStartUptimeMs = now lastPairingQrReaderStartUptimeMs = now
Log.v(TAG, "Start pairing QR camera")
Intent(this, CameraActivity::class.java).also { Intent(this, CameraActivity::class.java).also {
it.putExtra(CameraActivity.EXTRA_PAIRING_QR_CAMERA, true) it.putExtra(CameraActivity.EXTRA_PAIRING_QR_CAMERA, true)
startActivity(it) startActivity(it)
@@ -170,7 +170,6 @@ class CameraActivity : ComponentActivity() {
if (!::viewBinding.isInitialized) { if (!::viewBinding.isInitialized) {
return return
} }
Log.v(TAG, "onNewIntent: rebind pairing camera")
cleanupCameraResources() cleanupCameraResources()
qrHandledOrClosing.set(false) qrHandledOrClosing.set(false)
pairingQrDeliveredToQt = false pairingQrDeliveredToQt = false
@@ -1,206 +0,0 @@
package org.amnezia.vpn
import android.graphics.Color
import android.graphics.PixelFormat
import android.graphics.drawable.ColorDrawable
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.camera.core.Camera
import androidx.camera.core.CameraSelector
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.core.content.ContextCompat
import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.BarcodeScannerOptions.Builder
import com.google.mlkit.vision.barcode.ZoomSuggestionOptions
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
import org.amnezia.vpn.qt.QtAndroidController
import org.amnezia.vpn.util.Log
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
private const val TAG = "PairingQrEmbedded"
@OptIn(ExperimentalGetImage::class)
class PairingQrEmbeddedCamera(private val activity: AmneziaActivity) {
private var previewView: PreviewView? = null
private var cameraProvider: ProcessCameraProvider? = null
private var boundCamera: Camera? = null
private val checkedBarcodes = hashSetOf<String>()
private var barcodeScanner: com.google.mlkit.vision.barcode.BarcodeScanner? = null
private val windowBgOpaque = ColorDrawable(Color.parseColor("#0E0E11"))
private var savedWindowFormat: Int? = null
private var imageAnalysisExecutor: ExecutorService? = null
fun start() {
activity.runOnUiThread {
if (previewView != null) {
return@runOnUiThread
}
checkedBarcodes.clear()
activity.window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
val lp = activity.window.attributes
if (savedWindowFormat == null) {
savedWindowFormat = lp.format
}
lp.format = PixelFormat.TRANSLUCENT
activity.window.attributes = lp
val content = activity.findViewById<ViewGroup>(android.R.id.content)
content.clipChildren = false
content.clipToPadding = false
val pv = PreviewView(activity).apply {
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
// TextureView-style path so preview can show through “holes” in Qt Quick on some OEMs (e.g. Samsung).
implementationMode = PreviewView.ImplementationMode.COMPATIBLE
scaleType = PreviewView.ScaleType.FILL_CENTER
setBackgroundColor(Color.TRANSPARENT)
importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
}
content.addView(pv, 0)
for (i in 1 until content.childCount) {
content.getChildAt(i).bringToFront()
}
previewView = pv
val cameraProviderFuture = ProcessCameraProvider.getInstance(activity)
cameraProviderFuture.addListener({
try {
cameraProvider = cameraProviderFuture.get()
bindUseCases(pv)
pv.post {
pv.requestLayout()
pv.invalidate()
}
} catch (e: Exception) {
Log.e(TAG, "Camera bind failed: $e")
stop()
}
}, ContextCompat.getMainExecutor(activity))
}
}
private fun bindUseCases(viewFinder: PreviewView) {
val provider = cameraProvider ?: return
provider.unbindAll()
imageAnalysisExecutor?.shutdown()
imageAnalysisExecutor = Executors.newSingleThreadExecutor()
val preview = Preview.Builder().build().also {
it.setSurfaceProvider(viewFinder.surfaceProvider)
}
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
val cam = provider.bindToLifecycle(
activity,
CameraSelector.DEFAULT_BACK_CAMERA,
preview,
imageAnalysis
)
boundCamera = cam
barcodeScanner = BarcodeScanning.getClient(
Builder()
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
.setZoomSuggestionOptions(
ZoomSuggestionOptions.Builder { zoomLevel ->
activity.runOnUiThread {
try {
cam.cameraControl.setZoomRatio(zoomLevel)
} catch (e: Exception) {
Log.e(TAG, "Zoom: $e")
}
}
true
}.apply {
cam.cameraInfo.zoomState.value?.maxZoomRatio?.let { maxZoom ->
setMaxSupportedZoomRatio(maxZoom)
}
}.build()
).build()
)
val scanner = barcodeScanner!!
val analysisExecutor = imageAnalysisExecutor!!
imageAnalysis.setAnalyzer(analysisExecutor) { imageProxy ->
val mediaImage = imageProxy.image ?: run {
imageProxy.close()
return@setAnalyzer
}
val image = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
scanner.process(image)
.addOnSuccessListener(analysisExecutor) { barcodes ->
barcodes.firstOrNull()?.displayValue?.let { code ->
if (code.isNotEmpty() && code !in checkedBarcodes) {
checkedBarcodes.add(code)
if (QtAndroidController.decodeQrCode(code)) {
scanner.close()
barcodeScanner = null
activity.runOnUiThread { stop() }
}
}
}
}
.addOnFailureListener(analysisExecutor) {
Log.e(TAG, "QR process failed: ${it.message}")
}
.addOnCompleteListener(analysisExecutor) {
imageProxy.close()
}
}
}
fun setTorch(on: Boolean) {
activity.runOnUiThread {
try {
boundCamera?.cameraControl?.enableTorch(on)
} catch (e: Exception) {
Log.e(TAG, "Torch: $e")
}
}
}
fun stop() {
activity.runOnUiThread {
imageAnalysisExecutor?.shutdown()
imageAnalysisExecutor = null
try {
boundCamera?.cameraControl?.enableTorch(false)
} catch (_: Exception) {
}
boundCamera = null
barcodeScanner?.close()
barcodeScanner = null
cameraProvider?.unbindAll()
cameraProvider = null
previewView?.let { pv ->
(pv.parent as? ViewGroup)?.removeView(pv)
}
previewView = null
activity.window.setBackgroundDrawable(windowBgOpaque)
savedWindowFormat?.let { fmt ->
val lp = activity.window.attributes
lp.format = fmt
activity.window.attributes = lp
savedWindowFormat = null
}
}
}
}
@@ -250,21 +250,6 @@ void AndroidController::startPairingQrReaderActivity()
callActivityMethod("startPairingQrCodeReader", "()V"); callActivityMethod("startPairingQrCodeReader", "()V");
} }
void AndroidController::startPairingQrEmbeddedCamera()
{
callActivityMethod("startPairingQrEmbeddedCamera", "()V");
}
void AndroidController::stopPairingQrEmbeddedCamera()
{
callActivityMethod("stopPairingQrEmbeddedCamera", "()V");
}
void AndroidController::setPairingQrEmbeddedTorch(bool enabled)
{
callActivityMethod("setPairingQrEmbeddedTorch", "(Z)V", enabled);
}
void AndroidController::setSaveLogs(bool enabled) void AndroidController::setSaveLogs(bool enabled)
{ {
callActivityMethod("setSaveLogs", "(Z)V", enabled); callActivityMethod("setSaveLogs", "(Z)V", enabled);
@@ -47,9 +47,6 @@ public:
int getNavigationBarHeight(); int getNavigationBarHeight();
void startQrReaderActivity(); void startQrReaderActivity();
void startPairingQrReaderActivity(); void startPairingQrReaderActivity();
void startPairingQrEmbeddedCamera();
void stopPairingQrEmbeddedCamera();
void setPairingQrEmbeddedTorch(bool enabled);
void setSaveLogs(bool enabled); void setSaveLogs(bool enabled);
void exportLogsFile(const QString &fileName); void exportLogsFile(const QString &fileName);
void clearLogs(); void clearLogs();
+2 -90
View File
@@ -4,51 +4,15 @@
#include "platforms/ios/iosPairingCameraAccess.h" #include "platforms/ios/iosPairingCameraAccess.h"
#include <QByteArray> #include <QByteArray>
#include <QDebug>
#include <QThread>
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h> #import <AVFoundation/AVFoundation.h>
static NSString *amneziaQrThreadTag(void)
{
if ([NSThread isMainThread]) {
return @"main";
}
return [NSString stringWithFormat:@"bg:%p", (void *)[NSThread currentThread]];
}
static void amneziaQrLogDeviceAuth(void)
{
AVAuthorizationStatus st = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
NSString *stName = @"unknown";
switch (st) {
case AVAuthorizationStatusNotDetermined:
stName = @"notDetermined";
break;
case AVAuthorizationStatusRestricted:
stName = @"restricted";
break;
case AVAuthorizationStatusDenied:
stName = @"denied";
break;
case AVAuthorizationStatusAuthorized:
stName = @"authorized";
break;
default:
break;
}
NSLog(@"[QRCodeReader] camera auth status=%@ (%ld)", stName, (long)st);
}
static UIWindow *amneziaKeyWindowForQrCamera(void) static UIWindow *amneziaKeyWindowForQrCamera(void)
{ {
UIApplication *app = [UIApplication sharedApplication]; UIApplication *app = [UIApplication sharedApplication];
NSMutableArray<NSString *> *trace = [NSMutableArray array];
if (@available(iOS 13.0, *)) { if (@available(iOS 13.0, *)) {
NSInteger sceneCount = app.connectedScenes.count;
[trace addObject:[NSString stringWithFormat:@"connectedScenes=%ld", (long)sceneCount]];
for (UIScene *scene in app.connectedScenes) { for (UIScene *scene in app.connectedScenes) {
if (scene.activationState != UISceneActivationStateForegroundActive) { if (scene.activationState != UISceneActivationStateForegroundActive) {
continue; continue;
@@ -57,17 +21,13 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
continue; continue;
} }
UIWindowScene *windowScene = (UIWindowScene *)scene; UIWindowScene *windowScene = (UIWindowScene *)scene;
NSInteger winN = windowScene.windows.count;
[trace addObject:[NSString stringWithFormat:@"foreground UIWindowScene windows=%ld", (long)winN]];
for (UIWindow *window in windowScene.windows) { for (UIWindow *window in windowScene.windows) {
if (window.isKeyWindow) { if (window.isKeyWindow) {
NSLog(@"[QRCodeReader] keyWindow pick: scene keyWindow=%@ bounds=%@", window, NSStringFromCGRect(window.bounds));
return window; return window;
} }
} }
for (UIWindow *window in windowScene.windows) { for (UIWindow *window in windowScene.windows) {
if (!window.isHidden) { if (!window.isHidden) {
NSLog(@"[QRCodeReader] keyWindow pick: scene nonHidden=%@ bounds=%@", window, NSStringFromCGRect(window.bounds));
return window; return window;
} }
} }
@@ -75,25 +35,14 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
} }
if (app.keyWindow) { if (app.keyWindow) {
[trace addObject:@"app.keyWindow"];
NSLog(@"[QRCodeReader] keyWindow pick: application.keyWindow=%@ bounds=%@",
app.keyWindow, NSStringFromCGRect(app.keyWindow.bounds));
return app.keyWindow; return app.keyWindow;
} }
for (UIWindow *window in app.windows) { for (UIWindow *window in app.windows) {
if (window.isKeyWindow) { if (window.isKeyWindow) {
NSLog(@"[QRCodeReader] keyWindow pick: windows scan key=%@ bounds=%@", window, NSStringFromCGRect(window.bounds));
return window; return window;
} }
} }
UIWindow *first = app.windows.firstObject; return app.windows.firstObject;
if (first) {
NSLog(@"[QRCodeReader] keyWindow pick: firstObject=%@ bounds=%@ trace=[%@]",
first, NSStringFromCGRect(first.bounds), [trace componentsJoinedByString:@", "]);
return first;
}
NSLog(@"[QRCodeReader] keyWindow pick: NONE trace=[%@]", [trace componentsJoinedByString:@", "]);
return nil;
} }
@interface QRCodeReaderImpl : UIViewController @interface QRCodeReaderImpl : UIViewController
@@ -135,7 +84,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
if ([input isKindOfClass:[AVCaptureDeviceInput class]]) { if ([input isKindOfClass:[AVCaptureDeviceInput class]]) {
AVCaptureDevice *d = ((AVCaptureDeviceInput *)input).device; AVCaptureDevice *d = ((AVCaptureDeviceInput *)input).device;
if (d) { if (d) {
NSLog(@"[QRCodeReader] resolvedCaptureDevice from session input device=%p", d);
return d; return d;
} }
} }
@@ -155,13 +103,11 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
return; return;
} }
if (![device hasTorch]) { if (![device hasTorch]) {
NSLog(@"[QRCodeReader] torch: device %p has no torch", device);
return; return;
} }
AVCaptureSession *session = self.captureSession; AVCaptureSession *session = self.captureSession;
if (on && session && ![session isRunning]) { if (on && session && ![session isRunning]) {
NSLog(@"[QRCodeReader] torch: session not running yet; retry in 0.25s (session=%p)", session);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (on) { if (on) {
[self applyTorchOnMainThread:YES]; [self applyTorchOnMainThread:YES];
@@ -183,8 +129,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
if ([device isTorchModeSupported:AVCaptureTorchModeOn]) { if ([device isTorchModeSupported:AVCaptureTorchModeOn]) {
device.torchMode = AVCaptureTorchModeOn; device.torchMode = AVCaptureTorchModeOn;
} }
} else {
NSLog(@"[QRCodeReader] torch ON ok level=maxAvailable");
} }
} else { } else {
device.torchMode = AVCaptureTorchModeOff; device.torchMode = AVCaptureTorchModeOff;
@@ -193,7 +137,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
} }
- (void)applyTorch:(BOOL)on { - (void)applyTorch:(BOOL)on {
NSLog(@"[QRCodeReader] applyTorch requested on=%d thread=%@", (int)on, amneziaQrThreadTag());
if ([NSThread isMainThread]) { if ([NSThread isMainThread]) {
[self applyTorchOnMainThread:on]; [self applyTorchOnMainThread:on];
} else { } else {
@@ -204,9 +147,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
} }
- (BOOL)startReadingOnMainThread { - (BOOL)startReadingOnMainThread {
NSLog(@"[QRCodeReader] startReadingOnMainThread begin thread=%@", amneziaQrThreadTag());
amneziaQrLogDeviceAuth();
[self stopReadingOnMainThread]; [self stopReadingOnMainThread];
NSError *error = nil; NSError *error = nil;
@@ -216,7 +156,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
NSLog(@"[QRCodeReader] defaultDeviceWithMediaType:Video is nil"); NSLog(@"[QRCodeReader] defaultDeviceWithMediaType:Video is nil");
return NO; return NO;
} }
NSLog(@"[QRCodeReader] capture device=%p localizedName=%@", captureDevice, captureDevice.localizedName);
AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error]; AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
@@ -226,7 +165,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
} }
self.activeCaptureDevice = captureDevice; self.activeCaptureDevice = captureDevice;
NSLog(@"[QRCodeReader] activeCaptureDevice set to %p", self.activeCaptureDevice);
AVCaptureSession *session = [[AVCaptureSession alloc] init]; AVCaptureSession *session = [[AVCaptureSession alloc] init];
[session addInput:deviceInput]; [session addInput:deviceInput];
@@ -260,26 +198,16 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
self.videoPreviewPlayer.zPosition = -1000.f; self.videoPreviewPlayer.zPosition = -1000.f;
[keyWindow.layer insertSublayer:self.videoPreviewPlayer atIndex:0]; [keyWindow.layer insertSublayer:self.videoPreviewPlayer atIndex:0];
amneziaIosPairingRelayoutChromeIfNeeded(); amneziaIosPairingRelayoutChromeIfNeeded();
NSLog(@"[QRCodeReader] previewLayer inserted window=%@ layer.sublayers.count=%lu bounds=%@",
keyWindow, (unsigned long)keyWindow.layer.sublayers.count, NSStringFromCGRect(bounds));
AVCaptureSession *runningSession = self.captureSession; AVCaptureSession *runningSession = self.captureSession;
dispatch_async(_sessionQueue, ^{ dispatch_async(_sessionQueue, ^{
NSLog(@"[QRCodeReader] session startRunning on session queue session=%p", runningSession);
[runningSession startRunning]; [runningSession startRunning];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"[QRCodeReader] after startRunning isRunning=%d", (int)runningSession.isRunning);
}); });
});
NSLog(@"[QRCodeReader] startReading OK activeDevice=%p window bounds=%@",
self.activeCaptureDevice, NSStringFromCGRect(bounds));
return YES; return YES;
} }
- (BOOL)startReading { - (BOOL)startReading {
NSLog(@"[QRCodeReader] startReading entry thread=%@ qt=%p", amneziaQrThreadTag(), (void *)QThread::currentThread());
if ([NSThread isMainThread]) { if ([NSThread isMainThread]) {
return [self startReadingOnMainThread]; return [self startReadingOnMainThread];
} }
@@ -287,12 +215,10 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
dispatch_sync(dispatch_get_main_queue(), ^{ dispatch_sync(dispatch_get_main_queue(), ^{
ok = [self startReadingOnMainThread]; ok = [self startReadingOnMainThread];
}); });
NSLog(@"[QRCodeReader] startReading exit ok=%d (dispatched to main)", (int)ok);
return ok; return ok;
} }
- (void)stopReadingOnMainThread { - (void)stopReadingOnMainThread {
NSLog(@"[QRCodeReader] stopReadingOnMainThread thread=%@", amneziaQrThreadTag());
[self applyTorchOnMainThread:NO]; [self applyTorchOnMainThread:NO];
self.activeCaptureDevice = nil; self.activeCaptureDevice = nil;
@@ -311,7 +237,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
dispatch_sync(_sessionQueue, ^{ dispatch_sync(_sessionQueue, ^{
@try { @try {
if ([session isRunning]) { if ([session isRunning]) {
NSLog(@"[QRCodeReader] session stopRunning (sync) session=%p", session);
[session stopRunning]; [session stopRunning];
} }
} @catch (NSException *ex) { } @catch (NSException *ex) {
@@ -321,14 +246,12 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
} }
if (self.videoPreviewPlayer) { if (self.videoPreviewPlayer) {
NSLog(@"[QRCodeReader] remove preview from superlayer");
[self.videoPreviewPlayer removeFromSuperlayer]; [self.videoPreviewPlayer removeFromSuperlayer];
self.videoPreviewPlayer = nil; self.videoPreviewPlayer = nil;
} }
} }
- (void)stopReading { - (void)stopReading {
NSLog(@"[QRCodeReader] stopReading entry thread=%@ qt=%p", amneziaQrThreadTag(), (void *)QThread::currentThread());
if ([NSThread isMainThread]) { if ([NSThread isMainThread]) {
[self stopReadingOnMainThread]; [self stopReadingOnMainThread];
} else { } else {
@@ -336,7 +259,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
[self stopReadingOnMainThread]; [self stopReadingOnMainThread];
}); });
} }
NSLog(@"[QRCodeReader] stopReading exit");
} }
- (void)captureOutput:(AVCaptureOutput *)output - (void)captureOutput:(AVCaptureOutput *)output
@@ -351,7 +273,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
if (value.length == 0) { if (value.length == 0) {
return; return;
} }
NSLog(@"[QRCodeReader] metadata QR len=%lu", static_cast<unsigned long>(value.length));
QRCodeReader *cpp = _qrCodeReader; QRCodeReader *cpp = _qrCodeReader;
const QByteArray utf8([value UTF8String]); const QByteArray utf8([value UTF8String]);
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
@@ -374,21 +295,13 @@ QRect QRCodeReader::cameraSize() {
void QRCodeReader::setCameraSize(QRect value) { void QRCodeReader::setCameraSize(QRect value) {
m_cameraSize = value; m_cameraSize = value;
qInfo() << "[QRCodeReader] setCameraSize" << value;
} }
void QRCodeReader::startReading() { void QRCodeReader::startReading() {
qInfo() << "[QRCodeReader] C++ startReading thread" << QThread::currentThread(); [m_qrCodeReader startReading];
const BOOL ok = [m_qrCodeReader startReading];
if (!ok) {
qWarning() << "[QRCodeReader] C++ startReading failed (see NSLogs)";
} else {
qInfo() << "[QRCodeReader] C++ startReading ok";
}
} }
void QRCodeReader::stopReading() { void QRCodeReader::stopReading() {
qInfo() << "[QRCodeReader] C++ stopReading thread" << QThread::currentThread();
[m_qrCodeReader stopReading]; [m_qrCodeReader stopReading];
} }
@@ -397,7 +310,6 @@ void QRCodeReader::notifyCodeRead(const QString &code) {
} }
void QRCodeReader::setTorchEnabled(bool on) { void QRCodeReader::setTorchEnabled(bool on) {
qInfo() << "[QRCodeReader] C++ setTorchEnabled" << on << "thread" << QThread::currentThread();
[(QRCodeReaderImpl *)m_qrCodeReader applyTorch:on ? YES : NO]; [(QRCodeReaderImpl *)m_qrCodeReader applyTorch:on ? YES : NO];
} }
#else #else
@@ -130,11 +130,9 @@ static void amneziaLayoutPairingSafeAreaDimStrips(void)
CGFloat bottomY = hb.size.height - insets.bottom; CGFloat bottomY = hb.size.height - insets.bottom;
CGFloat bottomH = insets.bottom; CGFloat bottomH = insets.bottom;
UIView *qt = s_pairingDimQtRoot; UIView *qt = s_pairingDimQtRoot;
CGFloat qMaxYForLog = hb.size.height;
if (qt && qt.superview) { if (qt && qt.superview) {
CGRect qInHost = [host convertRect:qt.bounds fromView:qt]; CGRect qInHost = [host convertRect:qt.bounds fromView:qt];
const CGFloat qMaxY = CGRectGetMaxY(qInHost); const CGFloat qMaxY = CGRectGetMaxY(qInHost);
qMaxYForLog = qMaxY;
if (qMaxY < hb.size.height - 0.5f) { if (qMaxY < hb.size.height - 0.5f) {
bottomY = qMaxY; bottomY = qMaxY;
bottomH = hb.size.height - qMaxY; bottomH = hb.size.height - qMaxY;
@@ -168,28 +166,6 @@ static void amneziaLayoutPairingSafeAreaDimStrips(void)
s_pairingSafeBottomDim.frame = CGRectMake(0, bottomY, hb.size.width, bottomH); s_pairingSafeBottomDim.frame = CGRectMake(0, bottomY, hb.size.width, bottomH);
amneziaSyncPairingWindowBottomMaskLayer(w, host, bottomY, bottomH, hb.size.width); amneziaSyncPairingWindowBottomMaskLayer(w, host, bottomY, bottomH, hb.size.width);
CGRect winStrip = [w convertRect:s_pairingSafeBottomDim.frame fromView:host];
CALayer *previewLy = amneziaFindVideoPreviewLayerInWindow(w);
CGRect previewWin = CGRectZero;
if (previewLy) {
previewWin = [w.layer convertRect:previewLy.bounds fromLayer:previewLy];
}
CGRect maskWin = s_pairingWindowBottomMaskLayer ? s_pairingWindowBottomMaskLayer.frame : CGRectZero;
CGRect qtWin = CGRectZero;
if (qt) {
qtWin = [w convertRect:qt.bounds fromView:qt];
}
NSLog(@"[PairingCamera] safeAreaDim bottom strip y=%.1f h=%.1f qtMaxY=%.1f hostH=%.1f safeBottom=%.1f extraPt=%d winMask=%d",
bottomY, bottomH, qMaxYForLog, hb.size.height, insets.bottom, s_pairingNativeBottomExtraPt,
(int)(s_pairingWindowBottomMaskLayer.superlayer != nil));
NSLog(@"[PairingCamera] geom winStrip={{%.1f,%.1f},{%.1f,%.1f}} qtWin={{%.1f,%.1f},{%.1f,%.1f}} previewWin={{%.1f,%.1f},{%.1f,%.1f}} "
@"maskWin={{%.1f,%.1f},{%.1f,%.1f}} dyStripTopMinusPreviewTop=%.1f dyStripTopMinusQtMaxY=%.1f",
winStrip.origin.x, winStrip.origin.y, winStrip.size.width, winStrip.size.height, qtWin.origin.x, qtWin.origin.y,
qtWin.size.width, qtWin.size.height, previewWin.origin.x, previewWin.origin.y, previewWin.size.width,
previewWin.size.height, maskWin.origin.x, maskWin.origin.y, maskWin.size.width, maskWin.size.height,
CGRectGetMinY(winStrip) - CGRectGetMinY(previewWin), CGRectGetMinY(winStrip) - CGRectGetMaxY(qtWin));
} }
static void amneziaInstallPairingSafeAreaDimStrips(UIWindow *window, UIView *qtRootView) static void amneziaInstallPairingSafeAreaDimStrips(UIWindow *window, UIView *qtRootView)
@@ -227,10 +203,6 @@ static void amneziaInstallPairingSafeAreaDimStrips(UIWindow *window, UIView *qtR
[host insertSubview:s_pairingSafeTopDim belowSubview:qtRootView]; [host insertSubview:s_pairingSafeTopDim belowSubview:qtRootView];
[host insertSubview:s_pairingSafeBottomDim belowSubview:qtRootView]; [host insertSubview:s_pairingSafeBottomDim belowSubview:qtRootView];
NSLog(@"[PairingCamera] safeAreaDim host=%@ top=%.1f bottomInset=%.1f qtFrame=%@ hostBounds=%@ window=%@",
NSStringFromClass(host.class), insets.top, insets.bottom, NSStringFromCGRect(qtRootView.frame), NSStringFromCGRect(hb),
NSStringFromCGRect(window.bounds));
s_pairingSafeDimOrientationToken = [[NSNotificationCenter defaultCenter] s_pairingSafeDimOrientationToken = [[NSNotificationCenter defaultCenter]
addObserverForName:UIDeviceOrientationDidChangeNotification addObserverForName:UIDeviceOrientationDidChangeNotification
object:nil object:nil
@@ -277,7 +249,6 @@ static void amneziaForceMetalViewsTransparent(UIView *view, NSUInteger depth, NS
if (view.layer) { if (view.layer) {
view.layer.backgroundColor = [UIColor clearColor].CGColor; view.layer.backgroundColor = [UIColor clearColor].CGColor;
} }
NSLog(@"[PairingCamera] forceMetalTransparent depth=%lu class=%@", (unsigned long)depth, cn);
} }
if (depth < maxDepth) { if (depth < maxDepth) {
for (UIView *child in view.subviews) { for (UIView *child in view.subviews) {
@@ -358,7 +329,6 @@ void amneziaIosApplyEmbeddedCameraUnderlayToQtView(bool enable)
void (^work)(void) = ^{ void (^work)(void) = ^{
UIViewController *vc = amneziaKeyWindowViewController(); UIViewController *vc = amneziaKeyWindowViewController();
if (!vc || !vc.view) { if (!vc || !vc.view) {
NSLog(@"[PairingCamera] amneziaIosApplyEmbeddedCameraUnderlayToQtView: no root VC (enable=%d)", (int)enable);
return; return;
} }
UIView *root = vc.view; UIView *root = vc.view;
@@ -378,9 +348,6 @@ void amneziaIosApplyEmbeddedCameraUnderlayToQtView(bool enable)
amneziaInstallPairingSafeAreaDimStrips(win, root); amneziaInstallPairingSafeAreaDimStrips(win, root);
amneziaLayoutPairingSafeAreaDimStrips(); amneziaLayoutPairingSafeAreaDimStrips();
} }
NSLog(@"[PairingCamera] Qt view underlay transparency %@ subviews=%lu",
enable ? @"ON" : @"OFF", (unsigned long)root.subviews.count);
/** QUIMetalView is often updated after QSG resize; repeat walk next runloop so camera shows below status bar. */
if (enable) { if (enable) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
UIViewController *vc2 = amneziaKeyWindowViewController(); UIViewController *vc2 = amneziaKeyWindowViewController();
@@ -388,7 +355,6 @@ void amneziaIosApplyEmbeddedCameraUnderlayToQtView(bool enable)
return; return;
} }
amneziaApplyPairingUnderlayWalk(vc2.view, YES); amneziaApplyPairingUnderlayWalk(vc2.view, YES);
NSLog(@"[PairingCamera] underlay repeat pass (post-runloop)");
}); });
} }
}; };
@@ -17,56 +17,6 @@ static bool gTorchRequested = false;
/** Last time overlay became key; used to ignore restartCapture during warm-up (see QML kick timer removal). */ /** Last time overlay became key; used to ignore restartCapture during warm-up (see QML kick timer removal). */
static CFAbsoluteTime gPairingQrOverlayKeySince = -1.0; static CFAbsoluteTime gPairingQrOverlayKeySince = -1.0;
static void amneziaPairingQrLogScenes(NSString *tag)
{
NSUInteger n = 0;
for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
NSString *cls = NSStringFromClass(scene.class);
NSString *state = @"?";
switch (scene.activationState) {
case UISceneActivationStateUnattached:
state = @"unattached";
break;
case UISceneActivationStateForegroundActive:
state = @"foregroundActive";
break;
case UISceneActivationStateForegroundInactive:
state = @"foregroundInactive";
break;
case UISceneActivationStateBackground:
state = @"background";
break;
default:
break;
}
CGRect bounds = CGRectZero;
if ([scene isKindOfClass:[UIWindowScene class]]) {
bounds = ((UIWindowScene *)scene).coordinateSpace.bounds;
}
NSLog(@"[PairingQrOverlay] %@ scene[%lu] class=%@ state=%@ bounds=%@", tag, (unsigned long)n, cls, state,
NSStringFromCGRect(bounds));
n++;
}
if (n == 0) {
NSLog(@"[PairingQrOverlay] %@ connectedScenes count=0", tag);
}
}
static void amneziaPairingQrLogWindows(NSString *tag)
{
NSUInteger i = 0;
for (UIWindow *cw in UIApplication.sharedApplication.windows) {
NSLog(@"[PairingQrOverlay] %@ UIWindow[%lu] ptr=%p level=%.1f hidden=%d key=%d bounds=%@ rootVC=%@", tag,
(unsigned long)i, (void *)cw, cw.windowLevel, (int)cw.hidden, (int)cw.isKeyWindow,
NSStringFromCGRect(cw.bounds),
cw.rootViewController ? NSStringFromClass(cw.rootViewController.class) : @"(nil)");
i++;
}
if (i == 0) {
NSLog(@"[PairingQrOverlay] %@ UIApplication.windows count=0", tag);
}
}
static UIWindowScene *amneziaForegroundWindowScene(void) static UIWindowScene *amneziaForegroundWindowScene(void)
{ {
for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) { for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
@@ -113,11 +63,9 @@ static CGFloat amneziaPairingQrBottomTabStripReserve(UIWindowScene *scene)
if ([cw.rootViewController isKindOfClass:qios]) { if ([cw.rootViewController isKindOfClass:qios]) {
const CGFloat inset = cw.safeAreaInsets.bottom; const CGFloat inset = cw.safeAreaInsets.bottom;
const CGFloat reserve = inset + 49.f; const CGFloat reserve = inset + 49.f;
NSLog(@"[PairingQrOverlay] bottomReserve: QUIWindow safeBottom=%.1f -> reserve=%.1f", inset, reserve);
return MIN(MAX(reserve, 72.f), 140.f); return MIN(MAX(reserve, 72.f), 140.f);
} }
} }
NSLog(@"[PairingQrOverlay] bottomReserve: QUIWindow not found, default 83");
return 83.f; return 83.f;
} }
@@ -548,7 +496,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
- (void)backTapped - (void)backTapped
{ {
NSLog(@"[PairingQrOverlay] native back tapped");
if (gOnBack) { if (gOnBack) {
gOnBack(); gOnBack();
} }
@@ -557,7 +504,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
- (void)torchTapped - (void)torchTapped
{ {
gTorchRequested = !gTorchRequested; gTorchRequested = !gTorchRequested;
NSLog(@"[PairingQrOverlay] native torch toggle -> %d", (int)gTorchRequested);
[self applyTorchFromGlobalFlag]; [self applyTorchFromGlobalFlag];
if (gTorchRequested) { if (gTorchRequested) {
self.torchButton.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.42]; self.torchButton.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.42];
@@ -597,8 +543,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
{ {
AVCaptureDevice *device = self.videoDevice; AVCaptureDevice *device = self.videoDevice;
if (!device || ![device hasTorch]) { if (!device || ![device hasTorch]) {
NSLog(@"[PairingQrOverlay] applyTorch skipped on=%d device=%p hasTorch=%d", (int)on, (void *)device,
device ? (int)[device hasTorch] : -1);
if (on && gTorchRequested) { if (on && gTorchRequested) {
__unsafe_unretained AmneziaPairingQrOverlayViewController *unsafeSelf = self; __unsafe_unretained AmneziaPairingQrOverlayViewController *unsafeSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.12 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.12 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
@@ -676,9 +620,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
- (BOOL)startCapturePipelineOnMainThread - (BOOL)startCapturePipelineOnMainThread
{ {
NSLog(@"[PairingQrOverlay] startCapturePipelineOnMainThread entry camera.bounds=%@ thread=%@",
self.cameraContainer ? NSStringFromCGRect(self.cameraContainer.bounds) : @"(nil)",
[NSThread isMainThread] ? @"main" : @"bg");
[self stopCapturePipelineOnMainThread]; [self stopCapturePipelineOnMainThread];
@@ -693,8 +634,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
NSLog(@"[PairingQrOverlay] no default video device"); NSLog(@"[PairingQrOverlay] no default video device");
return NO; return NO;
} }
NSLog(@"[PairingQrOverlay] capture device=%p modelID=%@ localizedName=%@", (void *)device, device.modelID,
device.localizedName);
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error]; AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) { if (!input) {
NSLog(@"[PairingQrOverlay] deviceInput failed: %@", error.localizedDescription); NSLog(@"[PairingQrOverlay] deviceInput failed: %@", error.localizedDescription);
@@ -706,7 +645,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
if ([session canSetSessionPreset:AVCaptureSessionPresetHigh]) { if ([session canSetSessionPreset:AVCaptureSessionPresetHigh]) {
session.sessionPreset = AVCaptureSessionPresetHigh; session.sessionPreset = AVCaptureSessionPresetHigh;
} }
NSLog(@"[PairingQrOverlay] session preset=%@", session.sessionPreset);
[session addInput:input]; [session addInput:input];
@@ -732,7 +670,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
self.previewLayer = preview; self.previewLayer = preview;
[self.cameraContainer.layer insertSublayer:preview atIndex:0]; [self.cameraContainer.layer insertSublayer:preview atIndex:0];
preview.frame = self.cameraContainer.bounds; preview.frame = self.cameraContainer.bounds;
NSLog(@"[PairingQrOverlay] previewLayer on host frame=%@", NSStringFromCGRect(preview.frame));
[self.view layoutIfNeeded]; [self.view layoutIfNeeded];
[self layoutScanOverlayGeometry]; [self layoutScanOverlayGeometry];
@@ -740,22 +677,18 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
AVCaptureSession *runningSession = session; AVCaptureSession *runningSession = session;
__unsafe_unretained AmneziaPairingQrOverlayViewController *weakSelf = self; __unsafe_unretained AmneziaPairingQrOverlayViewController *weakSelf = self;
dispatch_async(q, ^{ dispatch_async(q, ^{
NSLog(@"[PairingQrOverlay] session queue: startRunning begin session=%p", (void *)runningSession);
@try { @try {
[runningSession startRunning]; [runningSession startRunning];
} @catch (NSException *ex) { } @catch (NSException *ex) {
NSLog(@"[PairingQrOverlay] startRunning exception: %@", ex); NSLog(@"[PairingQrOverlay] startRunning exception: %@", ex);
} }
const BOOL running = [runningSession isRunning]; const BOOL running = [runningSession isRunning];
NSLog(@"[PairingQrOverlay] session queue: startRunning end isRunning=%d", (int)running);
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
AmneziaPairingQrOverlayViewController *strongSelf = weakSelf; AmneziaPairingQrOverlayViewController *strongSelf = weakSelf;
if (!strongSelf) { if (!strongSelf) {
return; return;
} }
AVCaptureVideoPreviewLayer *pl = strongSelf.previewLayer; AVCaptureVideoPreviewLayer *pl = strongSelf.previewLayer;
NSLog(@"[PairingQrOverlay] post-start main: sessionRunning=%d previewFrame=%@ hostBounds=%@", (int)runningSession.isRunning,
pl ? NSStringFromCGRect(pl.frame) : @"(no preview)", NSStringFromCGRect(strongSelf.cameraContainer.bounds));
[strongSelf applyTorchFromGlobalFlag]; [strongSelf applyTorchFromGlobalFlag];
}); });
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.35 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.35 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
@@ -764,9 +697,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
return; return;
} }
AVCaptureVideoPreviewLayer *pl = strongSelf.previewLayer; AVCaptureVideoPreviewLayer *pl = strongSelf.previewLayer;
NSLog(@"[PairingQrOverlay] probe+350ms: sessionRunning=%d previewFrame=%@", (int)runningSession.isRunning,
pl ? NSStringFromCGRect(pl.frame) : @"(nil)");
amneziaPairingQrLogWindows(@"probe+350ms");
}); });
}); });
@@ -791,10 +721,8 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
} }
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
if (gOnScanned) { if (gOnScanned) {
NSLog(@"[PairingQrOverlay] metadata QR len=%lu", (unsigned long)copy.size());
gOnScanned(copy.c_str()); gOnScanned(copy.c_str());
} else { } else {
NSLog(@"[PairingQrOverlay] metadata QR but gOnScanned is nil");
} }
}); });
break; break;
@@ -805,7 +733,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
static void amneziaPairingQrOverlayTeardownOnMain(void) static void amneziaPairingQrOverlayTeardownOnMain(void)
{ {
NSLog(@"[PairingQrOverlay] teardownOnMain overlayPtr=%p", (void *)gPairingQrOverlayWindow);
UIWindow *w = gPairingQrOverlayWindow; UIWindow *w = gPairingQrOverlayWindow;
gPairingQrOverlayWindow = nil; gPairingQrOverlayWindow = nil;
gOnScanned = nullptr; gOnScanned = nullptr;
@@ -824,12 +751,9 @@ static void amneziaPairingQrOverlayTeardownOnMain(void)
UIWindow *restore = amneziaPickQtAppWindowToRestore(); UIWindow *restore = amneziaPickQtAppWindowToRestore();
if (restore) { if (restore) {
NSLog(@"[PairingQrOverlay] teardown: restore keyWindow to %@", restore);
[restore makeKeyWindow]; [restore makeKeyWindow];
} else { } else {
NSLog(@"[PairingQrOverlay] teardown: no window to restore as key");
} }
amneziaPairingQrLogWindows(@"afterTeardown");
} }
void amneziaIosPairingQrOverlayPresent(AmneziaPairingQrScannedUtf8Handler onScanned, AmneziaPairingQrOverlayBackHandler onBack, void amneziaIosPairingQrOverlayPresent(AmneziaPairingQrScannedUtf8Handler onScanned, AmneziaPairingQrOverlayBackHandler onBack,
@@ -837,26 +761,20 @@ void amneziaIosPairingQrOverlayPresent(AmneziaPairingQrScannedUtf8Handler onScan
{ {
const bool hasScan = static_cast<bool>(onScanned); const bool hasScan = static_cast<bool>(onScanned);
const bool hasBack = static_cast<bool>(onBack); const bool hasBack = static_cast<bool>(onBack);
NSLog(@"[PairingQrOverlay] C++ present requested hasScan=%d hasBack=%d callerThread=%p", (int)hasScan, (int)hasBack,
(void *)NSThread.currentThread);
AmneziaPairingQrScannedUtf8Handler scanH = std::move(onScanned); AmneziaPairingQrScannedUtf8Handler scanH = std::move(onScanned);
AmneziaPairingQrOverlayBackHandler backH = std::move(onBack); AmneziaPairingQrOverlayBackHandler backH = std::move(onBack);
const std::string titleCopy = titleUtf8; const std::string titleCopy = titleUtf8;
const std::string subCopy = subtitleUtf8; const std::string subCopy = subtitleUtf8;
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"[PairingQrOverlay] present block on main");
amneziaPairingQrLogScenes(@"beforeTeardown");
amneziaPairingQrLogWindows(@"beforeTeardown");
amneziaPairingQrOverlayTeardownOnMain(); amneziaPairingQrOverlayTeardownOnMain();
gOnScanned = std::move(scanH); gOnScanned = std::move(scanH);
gOnBack = std::move(backH); gOnBack = std::move(backH);
NSLog(@"[PairingQrOverlay] callbacks stored scan=%d back=%d", (int)(bool)gOnScanned, (int)(bool)gOnBack);
UIWindowScene *scene = amneziaForegroundWindowScene(); UIWindowScene *scene = amneziaForegroundWindowScene();
if (!scene) { if (!scene) {
NSLog(@"[PairingQrOverlay] present: no UIWindowScene"); NSLog(@"[PairingQrOverlay] present: no UIWindowScene");
amneziaPairingQrLogScenes(@"sceneNil");
gOnScanned = nullptr; gOnScanned = nullptr;
gOnBack = nullptr; gOnBack = nullptr;
return; return;
@@ -878,28 +796,22 @@ void amneziaIosPairingQrOverlayPresent(AmneziaPairingQrScannedUtf8Handler onScan
w.backgroundColor = [UIColor blackColor]; w.backgroundColor = [UIColor blackColor];
w.rootViewController = vc; w.rootViewController = vc;
gPairingQrOverlayWindow = w; gPairingQrOverlayWindow = w;
NSLog(@"[PairingQrOverlay] created UIWindow ptr=%p frame=%@ (sceneH=%.0f bottomReserve=%.0f) level=%.1f", (void *)w,
NSStringFromCGRect(w.frame), sceneBounds.size.height, bottomReserve, w.windowLevel);
[w makeKeyAndVisible]; [w makeKeyAndVisible];
[w layoutIfNeeded]; [w layoutIfNeeded];
[vc.view setNeedsLayout]; [vc.view setNeedsLayout];
[vc.view layoutIfNeeded]; [vc.view layoutIfNeeded];
NSLog(@"[PairingQrOverlay] after layout vc.view.bounds=%@ window.key=%d", NSStringFromCGRect(vc.view.bounds),
(int)w.isKeyWindow);
gPairingQrOverlayKeySince = CFAbsoluteTimeGetCurrent(); gPairingQrOverlayKeySince = CFAbsoluteTimeGetCurrent();
if (![vc startCapturePipelineOnMainThread]) { if (![vc startCapturePipelineOnMainThread]) {
NSLog(@"[PairingQrOverlay] startCapture failed"); NSLog(@"[PairingQrOverlay] startCapture failed");
} }
amneziaPairingQrLogWindows(@"afterPresent");
}); });
} }
void amneziaIosPairingQrOverlayDismiss() void amneziaIosPairingQrOverlayDismiss()
{ {
NSLog(@"[PairingQrOverlay] C++ dismiss requested");
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
amneziaPairingQrOverlayTeardownOnMain(); amneziaPairingQrOverlayTeardownOnMain();
}); });
@@ -907,12 +819,10 @@ void amneziaIosPairingQrOverlayDismiss()
void amneziaIosPairingQrOverlaySetTorchEnabled(bool on) void amneziaIosPairingQrOverlaySetTorchEnabled(bool on)
{ {
NSLog(@"[PairingQrOverlay] C++ setTorch=%d", (int)on);
gTorchRequested = on; gTorchRequested = on;
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
UIWindow *win = gPairingQrOverlayWindow; UIWindow *win = gPairingQrOverlayWindow;
if (!win) { if (!win) {
NSLog(@"[PairingQrOverlay] setTorch: no overlay window");
return; return;
} }
UIViewController *root = win.rootViewController; UIViewController *root = win.rootViewController;
@@ -935,25 +845,20 @@ void amneziaIosPairingQrOverlaySetTorchEnabled(bool on)
void amneziaIosPairingQrOverlayRestartCapture() void amneziaIosPairingQrOverlayRestartCapture()
{ {
NSLog(@"[PairingQrOverlay] C++ restartCapture requested");
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
const CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); const CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
if (gPairingQrOverlayKeySince > 0 && (now - gPairingQrOverlayKeySince) < 1.0) { if (gPairingQrOverlayKeySince > 0 && (now - gPairingQrOverlayKeySince) < 1.0) {
NSLog(@"[PairingQrOverlay] restartCapture: skipped warm-up (%.3fs since overlay key)", now - gPairingQrOverlayKeySince);
return; return;
} }
UIWindow *w = gPairingQrOverlayWindow; UIWindow *w = gPairingQrOverlayWindow;
if (!w) { if (!w) {
NSLog(@"[PairingQrOverlay] restartCapture: no overlay window");
return; return;
} }
UIViewController *root = w.rootViewController; UIViewController *root = w.rootViewController;
if (![root isKindOfClass:[AmneziaPairingQrOverlayViewController class]]) { if (![root isKindOfClass:[AmneziaPairingQrOverlayViewController class]]) {
NSLog(@"[PairingQrOverlay] restartCapture: rootVC class=%@", root ? NSStringFromClass(root.class) : @"(nil)");
return; return;
} }
AmneziaPairingQrOverlayViewController *vc = (AmneziaPairingQrOverlayViewController *)root; AmneziaPairingQrOverlayViewController *vc = (AmneziaPairingQrOverlayViewController *)root;
NSLog(@"[PairingQrOverlay] restartCapture: stop+start");
[vc stopCapturePipelineOnMainThread]; [vc stopCapturePipelineOnMainThread];
if (![vc startCapturePipelineOnMainThread]) { if (![vc startCapturePipelineOnMainThread]) {
NSLog(@"[PairingQrOverlay] restart startCapture failed"); NSLog(@"[PairingQrOverlay] restart startCapture failed");
@@ -3,7 +3,6 @@
#include <QCoreApplication> #include <QCoreApplication>
#include <QDataStream> #include <QDataStream>
#include <QDateTime> #include <QDateTime>
#include <QDebug>
#include <QIODevice> #include <QIODevice>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
@@ -241,7 +240,6 @@ void PairingUiController::clearPendingPhonePairingUuid()
void PairingUiController::openPairingQrScanner() void PairingUiController::openPairingQrScanner()
{ {
#if defined(Q_OS_ANDROID) #if defined(Q_OS_ANDROID)
qInfo() << "[PairingUi] openPairingQrScanner (Android native activity)";
AndroidController::instance()->startPairingQrReaderActivity(); AndroidController::instance()->startPairingQrReaderActivity();
#endif #endif
} }
@@ -251,9 +249,7 @@ bool PairingUiController::isPairingCameraAccessGranted() const
#if defined(Q_OS_ANDROID) #if defined(Q_OS_ANDROID)
return AndroidController::instance()->isCameraPermissionGranted(); return AndroidController::instance()->isCameraPermissionGranted();
#elif defined(Q_OS_IOS) #elif defined(Q_OS_IOS)
const bool ok = amneziaIosPairingCameraAccessGranted(); return amneziaIosPairingCameraAccessGranted();
qInfo() << "[PairingUi] iOS isPairingCameraAccessGranted =" << ok;
return ok;
#else #else
return true; return true;
#endif #endif
@@ -265,7 +261,6 @@ void PairingUiController::requestPairingCameraAccess()
AndroidController::instance()->requestCameraPermissionForQrPairing(); AndroidController::instance()->requestCameraPermissionForQrPairing();
#elif defined(Q_OS_IOS) #elif defined(Q_OS_IOS)
amneziaIosRequestPairingCameraAccess([this](bool granted) { amneziaIosRequestPairingCameraAccess([this](bool granted) {
qInfo() << "[PairingUi] iOS requestPairingCameraAccess callback granted =" << granted;
QMetaObject::invokeMethod( QMetaObject::invokeMethod(
this, [this, granted]() { emit pairingCameraAccessFinished(granted); }, Qt::QueuedConnection); this, [this, granted]() { emit pairingCameraAccessFinished(granted); }, Qt::QueuedConnection);
}); });
@@ -283,52 +278,10 @@ void PairingUiController::openPairingCameraAppSettings()
#endif #endif
} }
void PairingUiController::setEmbeddedPairingQrCameraActive(bool active)
{
if (m_embeddedPairingQrCameraActive == active) {
return;
}
m_embeddedPairingQrCameraActive = active;
#if defined(Q_OS_IOS)
qInfo() << "[PairingUi] iOS embeddedPairingQrCameraActive ->" << active;
amneziaIosApplyEmbeddedCameraUnderlayToQtView(active);
#endif
#if defined(Q_OS_ANDROID)
if (active) {
AndroidController::instance()->startPairingQrEmbeddedCamera();
} else {
AndroidController::instance()->stopPairingQrEmbeddedCamera();
}
#endif
emit embeddedPairingQrCameraActiveChanged();
}
void PairingUiController::syncIosEmbeddedPairingQrNativeBottomExtra(int extraPt)
{
#if defined(Q_OS_IOS)
amneziaIosSetPairingEmbeddedCameraNativeBottomExtraPt(extraPt);
#else
Q_UNUSED(extraPt);
#endif
}
void PairingUiController::refreshIosEmbeddedPairingQrChrome()
{
#if defined(Q_OS_IOS)
if (!m_embeddedPairingQrCameraActive) {
return;
}
qInfo() << "[PairingUi] refreshIosEmbeddedPairingQrChrome (reapply UIView underlay)";
amneziaIosApplyEmbeddedCameraUnderlayToQtView(true);
#else
// no-op on non-iOS
#endif
}
void PairingUiController::setPairingQrTorchEnabled(bool enabled) void PairingUiController::setPairingQrTorchEnabled(bool enabled)
{ {
#if defined(Q_OS_ANDROID) #if defined(Q_OS_ANDROID)
AndroidController::instance()->setPairingQrEmbeddedTorch(enabled); Q_UNUSED(enabled);
#elif defined(Q_OS_IOS) #elif defined(Q_OS_IOS)
amneziaIosPairingQrOverlaySetTorchEnabled(enabled); amneziaIosPairingQrOverlaySetTorchEnabled(enabled);
#else #else
@@ -339,7 +292,6 @@ void PairingUiController::setPairingQrTorchEnabled(bool enabled)
void PairingUiController::presentIosPairingQrNativeOverlayScanner(const QString &title, const QString &subtitle) void PairingUiController::presentIosPairingQrNativeOverlayScanner(const QString &title, const QString &subtitle)
{ {
#if defined(Q_OS_IOS) #if defined(Q_OS_IOS)
qInfo() << "[PairingUi] presentIosPairingQrNativeOverlayScanner: scheduling native UIWindow overlay";
const std::string titleUtf8 = title.isEmpty() ? std::string() : title.toStdString(); const std::string titleUtf8 = title.isEmpty() ? std::string() : title.toStdString();
const std::string subtitleUtf8 = subtitle.isEmpty() ? std::string() : subtitle.toStdString(); const std::string subtitleUtf8 = subtitle.isEmpty() ? std::string() : subtitle.toStdString();
amneziaIosPairingQrOverlayPresent( amneziaIosPairingQrOverlayPresent(
@@ -364,14 +316,12 @@ void PairingUiController::presentIosPairingQrNativeOverlayScanner(const QString
#else #else
Q_UNUSED(title); Q_UNUSED(title);
Q_UNUSED(subtitle); Q_UNUSED(subtitle);
qInfo() << "[PairingUi] presentIosPairingQrNativeOverlayScanner: no-op (not iOS build)";
#endif #endif
} }
void PairingUiController::dismissIosPairingQrNativeOverlayScanner() void PairingUiController::dismissIosPairingQrNativeOverlayScanner()
{ {
#if defined(Q_OS_IOS) #if defined(Q_OS_IOS)
qInfo() << "[PairingUi] dismissIosPairingQrNativeOverlayScanner";
amneziaIosPairingQrOverlayDismiss(); amneziaIosPairingQrOverlayDismiss();
#endif #endif
} }
@@ -379,25 +329,17 @@ void PairingUiController::dismissIosPairingQrNativeOverlayScanner()
void PairingUiController::restartIosPairingQrNativeOverlayCapture() void PairingUiController::restartIosPairingQrNativeOverlayCapture()
{ {
#if defined(Q_OS_IOS) #if defined(Q_OS_IOS)
qInfo() << "[PairingUi] restartIosPairingQrNativeOverlayCapture";
amneziaIosPairingQrOverlayRestartCapture(); amneziaIosPairingQrOverlayRestartCapture();
#endif #endif
} }
bool PairingUiController::applyScannedTextAsPairingUuid(const QString &raw) bool PairingUiController::applyScannedTextAsPairingUuid(const QString &raw)
{ {
const QString t = raw.trimmed();
qInfo() << "[PairingUi] scan raw len=" << t.size();
const QString uuid = extractPairingSessionUuidFromScanText(raw); const QString uuid = extractPairingSessionUuidFromScanText(raw);
if (uuid.isEmpty()) { if (uuid.isEmpty()) {
if (t.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) {
qInfo() << "[PairingUi] scan rejected: looks like vpn:// bundle, not session UUID";
} else {
qInfo() << "[PairingUi] scan rejected: no session UUID recognized in payload";
}
return false; return false;
} }
qInfo() << "[PairingUi] scan accepted uuid=" << uuid.left(13) << "...";
emit pairingUuidFromScan(uuid); emit pairingUuidFromScan(uuid);
return true; return true;
} }
@@ -406,7 +348,6 @@ bool PairingUiController::applyScannedTextAsPairingUuid(const QString &raw)
bool PairingUiController::tryConsumeAndroidQrScan(const QString &code) bool PairingUiController::tryConsumeAndroidQrScan(const QString &code)
{ {
if (!g_pairingUiForAndroidQr) { if (!g_pairingUiForAndroidQr) {
qWarning() << "[PairingUi] tryConsumeAndroidQrScan: no controller (g_pairingUiForAndroidQr null)";
return false; return false;
} }
const QString codeCopy = code; const QString codeCopy = code;
@@ -423,7 +364,6 @@ bool PairingUiController::tryConsumeAndroidQrScan(const QString &code)
} }
ctlPtr->applyScannedTextAsPairingUuid(codeCopy); ctlPtr->applyScannedTextAsPairingUuid(codeCopy);
}); });
qInfo() << "[PairingUi] tryConsumeAndroidQrScan: scheduled apply on Qt thread, rawLen=" << codeCopy.size();
return true; return true;
} }
@@ -743,8 +683,6 @@ void PairingUiController::cancelAllPairingActivity()
} }
cancelTvQrSession(); cancelTvQrSession();
setEmbeddedPairingQrCameraActive(false);
} }
void PairingUiController::submitPhonePairing(const QString &qrUuid, int serverIndex) void PairingUiController::submitPhonePairing(const QString &qrUuid, int serverIndex)
@@ -757,15 +695,12 @@ void PairingUiController::submitPhonePairing(const QString &qrUuid, int serverIn
} }
const QString trimmedUuid = qrUuid.trimmed(); const QString trimmedUuid = qrUuid.trimmed();
qInfo() << "[PairingUi] submitPhonePairing serverIndex=" << serverIndex << "uuidLen=" << trimmedUuid.size();
if (trimmedUuid.isEmpty()) { if (trimmedUuid.isEmpty()) {
qWarning() << "[PairingUi] submitPhonePairing aborted: empty UUID (paste or scan first)";
emit errorOccurred(ErrorCode::ApiConfigEmptyError); emit errorOccurred(ErrorCode::ApiConfigEmptyError);
return; return;
} }
if (serverIndex < 0 || serverIndex >= m_serversController->getServersCount()) { if (serverIndex < 0 || serverIndex >= m_serversController->getServersCount()) {
qWarning() << "[PairingUi] submitPhonePairing invalid serverIndex";
emit errorOccurred(ErrorCode::InternalError); emit errorOccurred(ErrorCode::InternalError);
return; return;
} }
@@ -773,7 +708,6 @@ void PairingUiController::submitPhonePairing(const QString &qrUuid, int serverIn
const QString serverId = m_serversController->getServerId(serverIndex); const QString serverId = m_serversController->getServerId(serverIndex);
const auto apiV2Opt = m_serversController->apiV2Config(serverId); const auto apiV2Opt = m_serversController->apiV2Config(serverId);
if (!apiV2Opt.has_value()) { if (!apiV2Opt.has_value()) {
qWarning() << "[PairingUi] submitPhonePairing server is not API v2";
emit errorOccurred(ErrorCode::InternalError); emit errorOccurred(ErrorCode::InternalError);
return; return;
} }
@@ -783,7 +717,6 @@ void PairingUiController::submitPhonePairing(const QString &qrUuid, int serverIn
QString vpnKey; QString vpnKey;
const ErrorCode keyErr = m_subscriptionController->prepareVpnKeyExport(serverId, vpnKey); const ErrorCode keyErr = m_subscriptionController->prepareVpnKeyExport(serverId, vpnKey);
if (keyErr != ErrorCode::NoError) { if (keyErr != ErrorCode::NoError) {
qWarning() << "[PairingUi] prepareVpnKeyExport failed" << static_cast<int>(keyErr);
emit errorOccurred(keyErr); emit errorOccurred(keyErr);
return; return;
} }
@@ -792,7 +725,6 @@ void PairingUiController::submitPhonePairing(const QString &qrUuid, int serverIn
const QJsonArray supportedProtocols = apiV2.apiConfig.supportedProtocols; const QJsonArray supportedProtocols = apiV2.apiConfig.supportedProtocols;
const QString apiKey = apiV2.authData.apiKey; const QString apiKey = apiV2.authData.apiKey;
if (apiKey.isEmpty()) { if (apiKey.isEmpty()) {
qWarning() << "[PairingUi] submitPhonePairing aborted: empty API key on server card";
emit errorOccurred(ErrorCode::ApiConfigEmptyError); emit errorOccurred(ErrorCode::ApiConfigEmptyError);
return; return;
} }
@@ -32,8 +32,6 @@ class PairingUiController : public QObject
pendingPhonePairingUuidChanged) pendingPhonePairingUuidChanged)
Q_PROPERTY(QString lastSuccessfulPhonePairingDisplayName READ lastSuccessfulPhonePairingDisplayName NOTIFY Q_PROPERTY(QString lastSuccessfulPhonePairingDisplayName READ lastSuccessfulPhonePairingDisplayName NOTIFY
lastSuccessfulPhonePairingDisplayNameChanged) lastSuccessfulPhonePairingDisplayNameChanged)
Q_PROPERTY(bool embeddedPairingQrCameraActive READ embeddedPairingQrCameraActive WRITE setEmbeddedPairingQrCameraActive NOTIFY
embeddedPairingQrCameraActiveChanged)
Q_PROPERTY(bool iosNativePairingQrOverlayBuild READ iosNativePairingQrOverlayBuild CONSTANT) Q_PROPERTY(bool iosNativePairingQrOverlayBuild READ iosNativePairingQrOverlayBuild CONSTANT)
Q_PROPERTY(bool androidNativePairingQrOverlayBuild READ androidNativePairingQrOverlayBuild CONSTANT) Q_PROPERTY(bool androidNativePairingQrOverlayBuild READ androidNativePairingQrOverlayBuild CONSTANT)
Q_PROPERTY(qint64 androidPairingReaderCooldownUntilEpochMs READ androidPairingReaderCooldownUntilEpochMs NOTIFY Q_PROPERTY(qint64 androidPairingReaderCooldownUntilEpochMs READ androidPairingReaderCooldownUntilEpochMs NOTIFY
@@ -57,12 +55,8 @@ public:
QString pendingPhonePairingUuid() const { return m_pendingPhonePairingUuid; } QString pendingPhonePairingUuid() const { return m_pendingPhonePairingUuid; }
void setPendingPhonePairingUuid(const QString &uuid); void setPendingPhonePairingUuid(const QString &uuid);
QString lastSuccessfulPhonePairingDisplayName() const { return m_lastSuccessfulPhonePairingDisplayName; } QString lastSuccessfulPhonePairingDisplayName() const { return m_lastSuccessfulPhonePairingDisplayName; }
bool embeddedPairingQrCameraActive() const { return m_embeddedPairingQrCameraActive; }
bool iosNativePairingQrOverlayBuild() const; bool iosNativePairingQrOverlayBuild() const;
bool androidNativePairingQrOverlayBuild() const; bool androidNativePairingQrOverlayBuild() const;
Q_INVOKABLE void setEmbeddedPairingQrCameraActive(bool active);
Q_INVOKABLE void syncIosEmbeddedPairingQrNativeBottomExtra(int extraPt);
Q_INVOKABLE void refreshIosEmbeddedPairingQrChrome();
qint64 androidPairingReaderCooldownUntilEpochMs() const { return m_androidPairingReaderCooldownUntilEpochMs; } qint64 androidPairingReaderCooldownUntilEpochMs() const { return m_androidPairingReaderCooldownUntilEpochMs; }
@@ -110,7 +104,6 @@ signals:
void pairingUuidFromScan(const QString &uuid); void pairingUuidFromScan(const QString &uuid);
void pairingCameraAccessFinished(bool granted); void pairingCameraAccessFinished(bool granted);
void embeddedPairingQrCameraActiveChanged();
void androidPairingReaderCooldownUntilEpochMsChanged(); void androidPairingReaderCooldownUntilEpochMsChanged();
void pairingSendQrScanRejectedInvalidPayload(); void pairingSendQrScanRejectedInvalidPayload();
void pairingIosNativeQrOverlayBackRequested(); void pairingIosNativeQrOverlayBackRequested();
@@ -149,7 +142,6 @@ private:
QPointer<QNetworkReply> m_phoneNetworkReply; QPointer<QNetworkReply> m_phoneNetworkReply;
quint64 m_phoneSessionGeneration { 0 }; quint64 m_phoneSessionGeneration { 0 };
bool m_embeddedPairingQrCameraActive = false;
qint64 m_androidPairingReaderCooldownUntilEpochMs = 0; qint64 m_androidPairingReaderCooldownUntilEpochMs = 0;
}; };
@@ -82,7 +82,6 @@ namespace PageLoader
PageSetupWizardApiPremiumInfo, PageSetupWizardApiPremiumInfo,
PageSetupWizardApiTrialEmail, PageSetupWizardApiTrialEmail,
PageSettingsApiQrPairingDev,
PageSettingsApiQrPairingSend, PageSettingsApiQrPairingSend,
PageSetupWizardApiQrPairingReceive, PageSetupWizardApiQrPairingReceive,
+1 -1
View File
@@ -4,7 +4,7 @@ import QtQuick.Controls
Menu { Menu {
property var textObj property var textObj
popupType: Popup.Native popupType: Qt.platform.os === "ios" ? Popup.Item : Popup.Native
onAboutToShow: blocker.enabled = true onAboutToShow: blocker.enabled = true
onClosed: blocker.enabled = false onClosed: blocker.enabled = false
@@ -53,10 +53,7 @@ TabButton {
background: Rectangle { background: Rectangle {
id: background id: background
anchors.fill: parent anchors.fill: parent
/** iOS embedded QR camera: clear window + preview under Qt; transparent tab cells show the camera. */ color: AmneziaStyle.color.transparent
color: (PairingUiController.embeddedPairingQrCameraActive && Qt.platform.os !== "android")
? AmneziaStyle.color.onyxBlack
: AmneziaStyle.color.transparent
radius: 10 radius: 10
border.color: root.activeFocus ? root.borderFocusedColor : AmneziaStyle.color.transparent border.color: root.activeFocus ? root.borderFocusedColor : AmneziaStyle.color.transparent
-15
View File
@@ -90,21 +90,6 @@ PageType {
footer: ColumnLayout { footer: ColumnLayout {
width: listView.width width: listView.width
LabelWithButtonType {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.rightMargin: 16
Layout.leftMargin: 16
text: qsTr("QR pairing (full dev UI)")
descriptionText: qsTr("Receive + send on one device for local gateway / QA")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsApiQrPairingDev)
}
}
SwitcherType { SwitcherType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 16
@@ -109,11 +109,11 @@ PageType {
} }
function onPhonePairingSucceeded() { function onPhonePairingSucceeded() {
const serverIndex = ServersUiController.getProcessedServerIndex() var serverId = ServersUiController.getServerId(ServersUiController.processedServerIndex)
if (serverIndex < 0) { if (serverId.length === 0) {
return return
} }
SubscriptionUiController.getAccountInfo(serverIndex, true) SubscriptionUiController.getAccountInfo(serverId, true)
SubscriptionUiController.updateApiDevicesModel() SubscriptionUiController.updateApiDevicesModel()
if (!root.visible) { if (!root.visible) {
return return
@@ -1,331 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QRCodeReader 1.0
import Style 1.0
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
import "../Components"
PageType {
id: root
property int qrImageIndex: 0
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 {
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()
}
}
}
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
ColumnLayout {
id: layout
width: root.width
spacing: 8
BackButtonType {
Layout.topMargin: 20 + PageController.safeAreaTopMargin
}
Label {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
text: qsTr("QR pairing (dev — single device)")
font.pixelSize: 28
font.bold: true
color: AmneziaStyle.color.paleGray
wrapMode: Text.Wrap
}
ParagraphTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
color: AmneziaStyle.color.goldenApricot
text: qsTr("Developer / QA: receive and send on one device (e.g. with local gateway). Not shown in production menus unless opened from Dev menu.")
wrapMode: Text.Wrap
}
ParagraphTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Experimental: transfer API configuration to another device via gateway. Use “Receive” on the device that shows the QR code, and “Send” on the premium device.")
wrapMode: Text.Wrap
}
Label {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 16
text: qsTr("Receive configuration (TV / second device)")
font.pixelSize: 18
font.bold: true
color: AmneziaStyle.color.mutedGray
}
BasicButtonType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
text: PairingUiController.tvPairingBusy ? qsTr("Waiting…") : qsTr("Start and show QR")
enabled: !PairingUiController.tvPairingBusy && !PairingUiController.phonePairingBusy
clickedFunc: function() {
PairingUiController.startTvQrSession()
}
}
BasicButtonType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Cancel receive")
enabled: PairingUiController.tvPairingBusy
clickedFunc: function() {
PairingUiController.cancelTvQrSession()
}
}
ParagraphTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: PairingUiController.tvStatusMessage.length > 0
text: PairingUiController.tvStatusMessage
wrapMode: Text.Wrap
}
Item {
id: qrBox
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
implicitHeight: width
visible: PairingUiController.tvQrCodesCount > 0
Image {
id: qrImage
anchors.fill: parent
fillMode: Image.PreserveAspectFit
sourceSize: Qt.size(2048, 2048)
source: PairingUiController.tvQrCodesCount > 0 ? PairingUiController.tvQrCodes[root.qrImageIndex] : ""
MouseArea {
anchors.fill: parent
enabled: PairingUiController.tvQrCodesCount > 1
onClicked: {
root.qrImageIndex = (root.qrImageIndex + 1) % PairingUiController.tvQrCodesCount
}
}
}
}
Label {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 24
text: qsTr("Send configuration (premium device)")
font.pixelSize: 18
font.bold: true
color: AmneziaStyle.color.mutedGray
}
TextFieldWithHeaderType {
id: uuidField
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("QR session UUID")
textField.placeholderText: qsTr("Paste UUID from TV QR")
}
BasicButtonType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: Qt.platform.os === "android" || Qt.platform.os === "ios"
text: {
if (Qt.platform.os === "ios" && root.pairingCameraOpen) {
return qsTr("Hide camera")
}
return qsTr("Scan QR code")
}
enabled: !PairingUiController.phonePairingBusy
clickedFunc: function() {
if (Qt.platform.os === "android") {
PairingUiController.openPairingQrScanner()
} else {
root.pairingCameraOpen = !root.pairingCameraOpen
}
}
}
Item {
id: cameraSlot
Layout.fillWidth: true
Layout.preferredHeight: (root.pairingCameraOpen && Qt.platform.os === "ios") ? 220 : 0
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: Layout.preferredHeight > 0
clip: true
QRCodeReader {
id: pairingQrReader
onCodeReaded: function(code) {
if (PairingUiController.applyScannedTextAsPairingUuid(code)) {
pairingQrReader.stopReading()
root.pairingCameraOpen = false
root.notifyPairingScanSuccess()
}
}
}
onVisibleChanged: {
if (!visible) {
pairingQrReader.stopReading()
return
}
if (Qt.platform.os === "ios") {
pairingCameraKickTimer.restart()
}
}
}
BasicButtonType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
text: PairingUiController.phonePairingBusy ? qsTr("Sending…") : qsTr("Send from current subscription")
enabled: !PairingUiController.phonePairingBusy
clickedFunc: function() {
PairingUiController.submitPhonePairing(uuidField.textField.text, ServersUiController.getProcessedServerIndex())
}
}
ParagraphTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 24 + PageController.safeAreaBottomMargin
visible: PairingUiController.phoneStatusMessage.length > 0
text: PairingUiController.phoneStatusMessage
wrapMode: Text.Wrap
}
}
}
Connections {
target: PairingUiController
function onTvQrCodesChanged() {
root.qrImageIndex = 0
}
function onTvSessionUuidChanged() {
root.qrImageIndex = 0
uuidField.textField.text = PairingUiController.tvSessionUuid
}
function onTvPairingConfigReceived() {
root.pairingCameraOpen = false
pairingQrReader.stopReading()
qrImage.source = ""
PageController.showNotificationMessage(qsTr("Configuration received from gateway"))
Qt.callLater(function() {
PageController.closePage()
})
}
function onPhonePairingSucceeded() {
root.pairingCameraOpen = false
pairingQrReader.stopReading()
PageController.showNotificationMessage(qsTr("Configuration sent"))
Qt.callLater(function() {
PageController.closePage()
})
}
function onPairingUuidFromScan(uuid) {
uuidField.textField.text = uuid
}
}
}
@@ -1,9 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Window
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import QRCodeReader 1.0
import PageEnum 1.0 import PageEnum 1.0
import Style 1.0 import Style 1.0
@@ -16,71 +14,12 @@ import "../Components"
PageType { PageType {
id: root id: root
property bool pairingQrChromeDebug: false
readonly property bool useIosStyleNativeQrReader: GC.isMobile() && Qt.platform.os !== "android"
readonly property bool useIosNativePairingQrOverlay: PairingUiController.iosNativePairingQrOverlayBuild readonly property bool useIosNativePairingQrOverlay: PairingUiController.iosNativePairingQrOverlayBuild
readonly property bool useAndroidNativePairingQrScanner: GC.isMobile() && Qt.platform.os === "android"
readonly property bool useAndroidNativePairingQrOverlay: PairingUiController.androidNativePairingQrOverlayBuild readonly property bool useAndroidNativePairingQrOverlay: PairingUiController.androidNativePairingQrOverlayBuild
&& GC.isMobile() && GC.isMobile()
&& Qt.platform.os === "android" && Qt.platform.os === "android"
readonly property bool extendScanDimToScreenEdges: GC.isMobile() && pairingWizardStep === 0
&& PairingUiController.embeddedPairingQrCameraActive
&& !root.useAndroidNativePairingQrOverlay
clip: !extendScanDimToScreenEdges
/** QQuickWindow as var — typing as Item breaks bindings on Qt 6. */
readonly property var appWindow: Window.window
readonly property real scanDimBleedTop: {
if (!extendScanDimToScreenEdges || !appWindow || !appWindow.contentItem)
return 0
let bleed = Math.max(0, root.mapToItem(appWindow.contentItem, 0, 0).y)
if (bleed < 2 && root.useIosStyleNativeQrReader)
bleed = Math.max(bleed, PageController.safeAreaTopMargin)
return bleed
}
readonly property real scanDimBleedBottom: {
if (!extendScanDimToScreenEdges || !appWindow || !appWindow.contentItem)
return 0
const o = root.mapToItem(appWindow.contentItem, 0, root.height)
let bleed = Math.max(0, appWindow.height - o.y)
const slack = Math.max(0, appWindow.height - root.height - scanDimBleedTop)
if (bleed < slack - 1)
bleed = Math.max(bleed, slack)
if (bleed < 2 && root.useIosStyleNativeQrReader)
bleed = Math.max(bleed, PageController.safeAreaBottomMargin + 72)
return bleed
}
readonly property real scanDimBleedBottomForDimLayer: (root.useIosStyleNativeQrReader
&& PairingUiController.embeddedPairingQrCameraActive) ? 0 : scanDimBleedBottom
function pushIosNativeBottomBleedSync() {
if (!root.useIosStyleNativeQrReader || !PairingUiController.embeddedPairingQrCameraActive) {
return
}
PairingUiController.syncIosEmbeddedPairingQrNativeBottomExtra(Math.max(0, Math.round(root.scanDimBleedBottom)))
}
onScanDimBleedBottomChanged: {
if (PairingUiController.embeddedPairingQrCameraActive && root.useIosStyleNativeQrReader) {
Qt.callLater(root.pushIosNativeBottomBleedSync)
}
}
Connections {
target: PairingUiController
function onEmbeddedPairingQrCameraActiveChanged() {
if (PairingUiController.embeddedPairingQrCameraActive) {
Qt.callLater(root.pushIosNativeBottomBleedSync)
}
}
}
property int pairingWizardStep: 0 property int pairingWizardStep: 0
property bool keepPhonePairingInBackgroundOnClose: false property bool keepPhonePairingInBackgroundOnClose: false
@@ -88,31 +27,17 @@ PageType {
property bool addDeviceConfirmNavigationScheduled: false property bool addDeviceConfirmNavigationScheduled: false
property bool awaitingCameraPermissionForScan: false property bool awaitingCameraPermissionForScan: false
property bool waitingSettingsReturnForScan: false property bool waitingSettingsReturnForScan: false
property bool torchOn: false
/** Suppress double startActivity when StackView fires both Component.onCompleted and onVisibleChanged. */ /** Suppress double startActivity when StackView fires both Component.onCompleted and onVisibleChanged. */
property int _androidPairingReaderLastStartMs: 0 property int _androidPairingReaderLastStartMs: 0
Timer {
id: pairingCameraKickTimer
interval: 220
repeat: false
onTriggered: root.restartPairingIosCamera()
}
function stopMobileScanner() { function stopMobileScanner() {
torchOn = false
if (root.useIosNativePairingQrOverlay) { if (root.useIosNativePairingQrOverlay) {
PairingUiController.setPairingQrTorchEnabled(false) PairingUiController.setPairingQrTorchEnabled(false)
PairingUiController.dismissIosPairingQrNativeOverlayScanner() PairingUiController.dismissIosPairingQrNativeOverlayScanner()
return } else if (Qt.platform.os === "android") {
}
if (Qt.platform.os === "android") {
PairingUiController.setPairingQrTorchEnabled(false) PairingUiController.setPairingQrTorchEnabled(false)
} else if (root.useIosStyleNativeQrReader) {
pairingQrReader.setTorchEnabled(false)
} }
pairingQrReader.stopReading()
PairingUiController.embeddedPairingQrCameraActive = false
} }
function startMobileScanner() { function startMobileScanner() {
@@ -122,7 +47,6 @@ PageType {
if (!root.visible) { if (!root.visible) {
return return
} }
/** Confirm step (or transition to it): never reopen native / embedded scanner from stray taps or visibility. */
if (root.pairingWizardStep !== 0) { if (root.pairingWizardStep !== 0) {
return return
} }
@@ -138,10 +62,9 @@ PageType {
PairingUiController.presentIosPairingQrNativeOverlayScanner( PairingUiController.presentIosPairingQrNativeOverlayScanner(
qsTr("Add device via QR"), qsTr("Add device via QR"),
qsTr("Scan the session QR shown on the device you want to add. You will confirm before the subscription is sent.")) qsTr("Scan the session QR shown on the device you want to add. You will confirm before the subscription is sent."))
/** Do not run pairingCameraKickTimer here: restartCapture during first startRunning races the session (torch needs 23 taps). */
return return
} }
if (root.useAndroidNativePairingQrScanner) { if (Qt.platform.os === "android") {
const coolUntil = PairingUiController.androidPairingReaderCooldownUntilEpochMs const coolUntil = PairingUiController.androidPairingReaderCooldownUntilEpochMs
if (Date.now() < coolUntil) { if (Date.now() < coolUntil) {
return return
@@ -152,23 +75,7 @@ PageType {
} }
_androidPairingReaderLastStartMs = now _androidPairingReaderLastStartMs = now
PairingUiController.openPairingQrScanner() PairingUiController.openPairingQrScanner()
return
} }
PairingUiController.embeddedPairingQrCameraActive = true
if (root.useIosStyleNativeQrReader) {
// Session must start here, not only after pairingCameraKickTimer (220ms), otherwise
// torch/scan run before startReading and native layer never attaches.
restartPairingIosCamera()
pairingCameraKickTimer.restart()
/** After resume embedded can stay true so setEmbedded skips; QUIMetalView may be opaque again. */
Qt.callLater(function () {
PairingUiController.refreshIosEmbeddedPairingQrChrome()
})
}
}
function startPairingScanAfterPermission() {
startMobileScanner()
} }
function showScanCameraDeniedDrawer() { function showScanCameraDeniedDrawer() {
@@ -195,20 +102,6 @@ PageType {
} }
} }
function restartPairingIosCamera() {
if (root.useIosNativePairingQrOverlay) {
PairingUiController.restartIosPairingQrNativeOverlayCapture()
return
}
if (!root.useIosStyleNativeQrReader || pairingWizardStep !== 0) {
return
}
// Never gate on root.visible here: under StackView the active page often has
// visible === false while it is on screen, so startReading never ran (no session, no torch).
pairingQrReader.stopReading()
pairingQrReader.startReading()
}
Component.onDestruction: { Component.onDestruction: {
if (!keepPhonePairingInBackgroundOnClose && !PairingUiController.phonePairingBusy) { if (!keepPhonePairingInBackgroundOnClose && !PairingUiController.phonePairingBusy) {
PairingUiController.cancelAllPairingActivity() PairingUiController.cancelAllPairingActivity()
@@ -217,18 +110,16 @@ PageType {
onVisibleChanged: { onVisibleChanged: {
if (visible) { if (visible) {
/** Only reset confirm flag on scan step; clearing it on confirm breaks guards if visible flickers. */
if (pairingWizardStep === 0) { if (pairingWizardStep === 0) {
addDeviceConfirmNavigationScheduled = false addDeviceConfirmNavigationScheduled = false
Qt.callLater(startMobileScanner) Qt.callLater(startMobileScanner)
} }
} else { } else if (!PairingUiController.phonePairingBusy) {
pairingCameraKickTimer.stop()
stopMobileScanner() stopMobileScanner()
_androidPairingReaderLastStartMs = 0 _androidPairingReaderLastStartMs = 0
pairingWizardStep = 0 pairingWizardStep = 0
waitingSettingsReturnForScan = false waitingSettingsReturnForScan = false
if (!keepPhonePairingInBackgroundOnClose && !PairingUiController.phonePairingBusy) { if (!keepPhonePairingInBackgroundOnClose) {
PairingUiController.cancelAllPairingActivity() PairingUiController.cancelAllPairingActivity()
} }
} }
@@ -238,16 +129,12 @@ PageType {
if (pairingWizardStep !== 0) { if (pairingWizardStep !== 0) {
stopMobileScanner() stopMobileScanner()
} else if (root.visible) { } else if (root.visible) {
/**
* Android native: use Qt.callLater like iOS — a multi-second Timer delay left the QML scan chrome
* visible with an empty (black) viewport until CameraActivity opened.
*/
Qt.callLater(startMobileScanner) Qt.callLater(startMobileScanner)
} }
} }
Component.onCompleted: { Component.onCompleted: {
if (GC.isMobile() && root.visible && pairingWizardStep === 0) { if (visible && pairingWizardStep === 0) {
Qt.callLater(startMobileScanner) Qt.callLater(startMobileScanner)
} }
} }
@@ -260,38 +147,16 @@ PageType {
return return
} }
root.tryResumeScanAfterCameraSettings() root.tryResumeScanAfterCameraSettings()
if (!root.useIosStyleNativeQrReader || root.pairingWizardStep !== 0 if (!root.useIosNativePairingQrOverlay || root.pairingWizardStep !== 0
|| !PairingUiController.isPairingCameraAccessGranted()) { || !PairingUiController.isPairingCameraAccessGranted()) {
return return
} }
if (root.useIosNativePairingQrOverlay) {
Qt.callLater(function () { Qt.callLater(function () {
if (!root.visible || root.pairingWizardStep !== 0 || !GC.isMobile()) { if (!root.visible || root.pairingWizardStep !== 0 || !GC.isMobile()) {
return return
} }
PairingUiController.restartIosPairingQrNativeOverlayCapture() PairingUiController.restartIosPairingQrNativeOverlayCapture()
}) })
return
}
Qt.callLater(function () {
if (!root.visible || root.pairingWizardStep !== 0 || !GC.isMobile()) {
return
}
PairingUiController.embeddedPairingQrCameraActive = true
PairingUiController.refreshIosEmbeddedPairingQrChrome()
Qt.callLater(function () {
if (!root.visible || root.pairingWizardStep !== 0) {
return
}
root.restartPairingIosCamera()
Qt.callLater(function () {
if (!root.visible || root.pairingWizardStep !== 0) {
return
}
PairingUiController.refreshIosEmbeddedPairingQrChrome()
})
})
})
} }
} }
@@ -308,15 +173,15 @@ PageType {
Item { Item {
anchors.fill: parent anchors.fill: parent
/** Brief Qt backdrop + back while CameraActivity is starting (native holds title/instructions like iOS overlay). */
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
visible: pairingWizardStep === 0 && root.useAndroidNativePairingQrOverlay visible: pairingWizardStep === 0 && root.useAndroidNativePairingQrOverlay
color: AmneziaStyle.color.midnightBlack color: AmneziaStyle.color.midnightBlack
z: 1 z: 1
} }
BackButtonType { BackButtonType {
visible: pairingWizardStep === 0 && root.useAndroidNativePairingQrOverlay visible: pairingWizardStep === 0 && (root.useAndroidNativePairingQrOverlay || !GC.isMobile())
anchors.top: parent.top anchors.top: parent.top
anchors.topMargin: PageController.safeAreaTopMargin anchors.topMargin: PageController.safeAreaTopMargin
anchors.left: parent.left anchors.left: parent.left
@@ -327,312 +192,25 @@ PageType {
} }
} }
Item { Column {
id: scanStep
anchors.fill: parent
visible: pairingWizardStep === 0 && !root.useAndroidNativePairingQrOverlay
/** Extra guard: invisible alone can race one frame on some stacks; deny input off scan step. */
enabled: pairingWizardStep === 0 && !root.useAndroidNativePairingQrOverlay
readonly property real sqSz: Math.floor(Math.min(width, height) * 0.72)
readonly property real sqX: (width - sqSz) / 2
readonly property real sqY: (height - sqSz) / 2 - height * 0.06
readonly property real dimAlpha: 0.55
readonly property color dimTopDebug: "#aa3333"
readonly property color dimBottomDebug: "#33aaff"
readonly property color dimLeftDebug: "#3333ff"
readonly property color dimRightDebug: "#ffaa33"
readonly property int bracketThick: 5
readonly property int bracketLen: Math.max(28, Math.floor(sqSz * 0.13))
readonly property real bracketRadius: bracketThick * 0.5
Rectangle {
anchors.fill: parent
color: AmneziaStyle.color.midnightBlack
visible: !GC.isMobile()
}
Label {
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width - 48 width: parent.width - 48
spacing: 12
visible: pairingWizardStep === 0 && !GC.isMobile()
Label {
width: parent.width
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap wrapMode: Text.Wrap
visible: !GC.isMobile()
color: AmneziaStyle.color.mutedGray color: AmneziaStyle.color.mutedGray
font.pixelSize: 15 font.pixelSize: 15
text: qsTr("QR pairing is available in the mobile app.") text: qsTr("QR pairing is available in the mobile app.")
} }
Item {
id: dimLayer
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: -root.scanDimBleedTop
anchors.bottom: parent.bottom
anchors.bottomMargin: -root.scanDimBleedBottomForDimLayer
visible: GC.isMobile()
z: 0
readonly property real holeTop: root.scanDimBleedTop + scanStep.sqY
readonly property real holeBottom: holeTop + scanStep.sqSz
Rectangle {
x: 0
y: 0
width: parent.width
height: Math.max(0, dimLayer.holeTop)
color: root.pairingQrChromeDebug ? scanStep.dimTopDebug : Qt.rgba(0, 0, 0, scanStep.dimAlpha)
}
Rectangle {
x: 0
y: dimLayer.holeBottom
width: parent.width
height: Math.max(0, dimLayer.height - dimLayer.holeBottom)
color: root.pairingQrChromeDebug ? scanStep.dimBottomDebug : Qt.rgba(0, 0, 0, scanStep.dimAlpha)
}
Rectangle {
x: 0
y: dimLayer.holeTop
width: Math.max(0, scanStep.sqX)
height: scanStep.sqSz
color: root.pairingQrChromeDebug ? scanStep.dimLeftDebug : Qt.rgba(0, 0, 0, scanStep.dimAlpha)
}
Rectangle {
x: scanStep.sqX + scanStep.sqSz
y: dimLayer.holeTop
width: Math.max(0, parent.width - (scanStep.sqX + scanStep.sqSz))
height: scanStep.sqSz
color: root.pairingQrChromeDebug ? scanStep.dimRightDebug : Qt.rgba(0, 0, 0, scanStep.dimAlpha)
}
}
/** Same onyx as tab bar: bridges dim/camera to TabBar sibling so the seam is not only TabBar.background overlap. */
Rectangle {
id: pairingIosStackBottomChromeBridge
objectName: "pairingIosStackBottomChromeBridge"
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: 22
visible: GC.isMobile() && root.useIosStyleNativeQrReader && PairingUiController.embeddedPairingQrCameraActive
color: root.pairingQrChromeDebug ? "#8844ff" : AmneziaStyle.color.onyxBlack
z: 1
}
Item {
x: scanStep.sqX
y: scanStep.sqY
width: scanStep.sqSz
height: scanStep.sqSz
visible: GC.isMobile()
Rectangle {
x: 0
y: 0
width: scanStep.bracketLen
height: scanStep.bracketThick
radius: scanStep.bracketRadius
color: AmneziaStyle.color.paleGray
}
Rectangle {
x: 0
y: 0
width: scanStep.bracketThick
height: scanStep.bracketLen
radius: scanStep.bracketRadius
color: AmneziaStyle.color.paleGray
}
Rectangle {
x: scanStep.sqSz - scanStep.bracketLen
y: 0
width: scanStep.bracketLen
height: scanStep.bracketThick
radius: scanStep.bracketRadius
color: AmneziaStyle.color.paleGray
}
Rectangle {
x: scanStep.sqSz - scanStep.bracketThick
y: 0
width: scanStep.bracketThick
height: scanStep.bracketLen
radius: scanStep.bracketRadius
color: AmneziaStyle.color.paleGray
}
Rectangle {
x: 0
y: scanStep.sqSz - scanStep.bracketThick
width: scanStep.bracketLen
height: scanStep.bracketThick
radius: scanStep.bracketRadius
color: AmneziaStyle.color.paleGray
}
Rectangle {
x: 0
y: scanStep.sqSz - scanStep.bracketLen
width: scanStep.bracketThick
height: scanStep.bracketLen
radius: scanStep.bracketRadius
color: AmneziaStyle.color.paleGray
}
Rectangle {
x: scanStep.sqSz - scanStep.bracketLen
y: scanStep.sqSz - scanStep.bracketThick
width: scanStep.bracketLen
height: scanStep.bracketThick
radius: scanStep.bracketRadius
color: AmneziaStyle.color.paleGray
}
Rectangle {
x: scanStep.sqSz - scanStep.bracketThick
y: scanStep.sqSz - scanStep.bracketLen
width: scanStep.bracketThick
height: scanStep.bracketLen
radius: scanStep.bracketRadius
color: AmneziaStyle.color.paleGray
}
}
Column {
id: headerBlock
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 8 + PageController.safeAreaTopMargin
spacing: 10
z: 2
/** Native iOS overlay draws its own header. */
visible: !GC.isMobile() || !root.useIosNativePairingQrOverlay
BackButtonType {
width: parent.width
backButtonFunction: function() {
PageController.closePage()
}
}
Label {
width: parent.width - 32
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("Add device via QR")
font.pixelSize: 28
font.bold: true
color: AmneziaStyle.color.paleGray
wrapMode: Text.Wrap
}
ParagraphTextType {
width: parent.width - 32
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("Scan the session QR shown on the device you want to add. You will confirm before the subscription is sent.")
wrapMode: Text.Wrap
}
}
Item {
z: 2
width: 56
height: 56
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottomMargin: 28 + PageController.safeAreaBottomMargin
visible: GC.isMobile() && !root.useIosNativePairingQrOverlay
Rectangle {
anchors.fill: parent
radius: 28
color: Qt.rgba(1, 1, 1, root.torchOn ? 0.42 : 0.22)
border.width: root.torchOn ? 2 : 0
border.color: AmneziaStyle.color.goldenApricot
}
Text {
anchors.centerIn: parent
text: "🔦"
font.pixelSize: 26
}
MouseArea {
anchors.fill: parent
onClicked: {
root.torchOn = !root.torchOn
if (Qt.platform.os === "android") {
PairingUiController.setPairingQrTorchEnabled(root.torchOn)
} else if (root.useIosNativePairingQrOverlay) {
PairingUiController.setPairingQrTorchEnabled(root.torchOn)
} else if (root.useIosStyleNativeQrReader) {
pairingQrReader.setTorchEnabled(root.torchOn)
}
}
}
}
ParagraphTextType {
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottomMargin: 100 + PageController.safeAreaBottomMargin
anchors.leftMargin: 16
anchors.rightMargin: 16
visible: PairingUiController.phoneStatusMessage.length > 0
text: PairingUiController.phoneStatusMessage
wrapMode: Text.Wrap
z: 2
}
Item {
width: 0
height: 0
visible: false
QRCodeReader {
id: pairingQrReader
// Same idea as PageSetupWizardQrReader: ensure startReading runs even if
// StackView/onVisible timing skips startMobileScanner once.
Component.onCompleted: {
if (root.useIosNativePairingQrOverlay) {
return
}
if (!root.useIosStyleNativeQrReader || root.pairingWizardStep !== 0) {
return
}
Qt.callLater(function () {
if (root.pairingWizardStep !== 0 || !PairingUiController.isPairingCameraAccessGranted()) {
return
}
PairingUiController.embeddedPairingQrCameraActive = true
pairingQrReader.stopReading()
pairingQrReader.startReading()
})
}
onCodeReaded: function(code) {
if (addDeviceConfirmNavigationScheduled) {
return
}
if (PairingUiController.applyScannedTextAsPairingUuid(code)) {
addDeviceConfirmNavigationScheduled = true
stopMobileScanner()
} else {
const now = new Date().getTime()
if (now - lastInvalidPairingQrToastClockMs >= 2200) {
lastInvalidPairingQrToastClockMs = now
PageController.showNotificationMessage(
qsTr("This QR code is not a pairing session. Show the code from the other devices “receive config” screen."))
}
}
}
}
}
} }
ColumnLayout { ColumnLayout {
id: confirmStep id: confirmStep
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: 0
anchors.rightMargin: 0
visible: pairingWizardStep === 1 visible: pairingWizardStep === 1
z: 10 z: 10
spacing: 16 spacing: 16
@@ -732,7 +310,6 @@ PageType {
addDeviceConfirmNavigationScheduled = true addDeviceConfirmNavigationScheduled = true
stopMobileScanner() stopMobileScanner()
PairingUiController.pendingPhonePairingUuid = uuid PairingUiController.pendingPhonePairingUuid = uuid
/** Immediate step switch so scan chrome is not hit-testable for another frame (avoids reopening CameraActivity). */
pairingWizardStep = 1 pairingWizardStep = 1
} }
@@ -753,7 +330,6 @@ PageType {
PageController.closePage() PageController.closePage()
} }
/** Native CameraActivity back: leave pairing flow (same as iOS overlay back). Do NOT reopen scanner. */
function onPairingAndroidNativeQrScannerUserDismissed() { function onPairingAndroidNativeQrScannerUserDismissed() {
if (!root.useAndroidNativePairingQrOverlay) { if (!root.useAndroidNativePairingQrOverlay) {
return return
@@ -12,7 +12,6 @@ import "../Components"
PageType { PageType {
id: root id: root
property int qrImageIndex: 0
readonly property int qrRefreshIntervalMs: Math.max(5000, PairingUiController.tvPairingWaitWindowSeconds * 1000) readonly property int qrRefreshIntervalMs: Math.max(5000, PairingUiController.tvPairingWaitWindowSeconds * 1000)
function scrollPairingToBottom() { function scrollPairingToBottom() {
@@ -134,15 +133,7 @@ PageType {
anchors.margins: 20 anchors.margins: 20
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
sourceSize: Qt.size(2048, 2048) sourceSize: Qt.size(2048, 2048)
source: PairingUiController.tvQrCodesCount > 0 ? PairingUiController.tvQrCodes[root.qrImageIndex] : "" source: PairingUiController.tvQrCodesCount > 0 ? PairingUiController.tvQrCodes[0] : ""
MouseArea {
anchors.fill: parent
enabled: PairingUiController.tvQrCodesCount > 1
onClicked: {
root.qrImageIndex = (root.qrImageIndex + 1) % PairingUiController.tvQrCodesCount
}
}
} }
} }
} }
@@ -170,7 +161,6 @@ PageType {
target: PairingUiController target: PairingUiController
function onTvQrCodesChanged() { function onTvQrCodesChanged() {
root.qrImageIndex = 0
if (PairingUiController.tvQrCodesCount > 0) { if (PairingUiController.tvQrCodesCount > 0) {
scrollToBottomRetryTimer.retries = 0 scrollToBottomRetryTimer.retries = 0
scrollToBottomRetryTimer.start() scrollToBottomRetryTimer.start()
@@ -185,10 +175,6 @@ PageType {
} }
} }
function onTvSessionUuidChanged() {
root.qrImageIndex = 0
}
function onTvPairingConfigReceived() { function onTvPairingConfigReceived() {
scrollToBottomRetryTimer.stop() scrollToBottomRetryTimer.stop()
qrRotationTimer.stop() qrRotationTimer.stop()
+4 -13
View File
@@ -19,15 +19,6 @@ PageType {
property bool isControlsDisabled: false property bool isControlsDisabled: false
property bool isTabBarDisabled: false property bool isTabBarDisabled: false
property bool pairingQrChromeDebug: false
readonly property int tabBarChromeOverlapUp: (PairingUiController.embeddedPairingQrCameraActive && GC.isMobile()
&& Qt.platform.os !== "android")
? (root.pairingQrChromeDebug ? 24 : 18) : 0
readonly property int tabStackPairingUnderlapDown: (PairingUiController.embeddedPairingQrCameraActive && GC.isMobile()
&& Qt.platform.os !== "android") ? 8 : 0
Connections { Connections {
objectName: "pageControllerConnection" objectName: "pageControllerConnection"
@@ -279,7 +270,7 @@ PageType {
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
anchors.bottom: tabBar.top anchors.bottom: tabBar.top
anchors.bottomMargin: -root.tabStackPairingUnderlapDown anchors.bottomMargin: 0
enabled: !root.isControlsDisabled enabled: !root.isControlsDisabled
@@ -351,16 +342,16 @@ PageType {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
height: parent.height + root.tabBarChromeOverlapUp height: parent.height
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
color: root.pairingQrChromeDebug ? "#00ff66" : AmneziaStyle.color.onyxBlack color: AmneziaStyle.color.onyxBlack
} }
Shape { Shape {
id: tabBarChromeShape id: tabBarChromeShape
objectName: "backgroundShape" objectName: "backgroundShape"
visible: root.tabBarChromeOverlapUp === 0 visible: true
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
+1 -2
View File
@@ -16,8 +16,7 @@ Window {
id: root id: root
objectName: "mainWindow" objectName: "mainWindow"
readonly property bool pairingQrCameraUnderlay: PairingUiController.embeddedPairingQrCameraActive && GC.isMobile() color: AmneziaStyle.color.midnightBlack
color: pairingQrCameraUnderlay ? "#00000000" : AmneziaStyle.color.midnightBlack
Connections { Connections {
target: Qt.application target: Qt.application
-1
View File
@@ -97,7 +97,6 @@
<file>Pages2/PageSettingsAbout.qml</file> <file>Pages2/PageSettingsAbout.qml</file>
<file>Pages2/PageSettingsApiAvailableCountries.qml</file> <file>Pages2/PageSettingsApiAvailableCountries.qml</file>
<file>Pages2/PageSettingsApiServerInfo.qml</file> <file>Pages2/PageSettingsApiServerInfo.qml</file>
<file>Pages2/PageSettingsApiQrPairingDev.qml</file>
<file>Pages2/PageSettingsApiQrPairingSend.qml</file> <file>Pages2/PageSettingsApiQrPairingSend.qml</file>
<file>Pages2/PageSetupWizardApiQrPairingReceive.qml</file> <file>Pages2/PageSetupWizardApiQrPairingReceive.qml</file>
<file>Pages2/PageSettingsApplication.qml</file> <file>Pages2/PageSettingsApplication.qml</file>