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 openFileDeliveryScheduled = false
private var pairingQrEmbeddedCamera: PairingQrEmbeddedCamera? = null
private var lastPairingQrReaderStartUptimeMs: Long = 0L
private val vpnServiceEventHandler: Handler by lazy(NONE) {
@@ -998,26 +997,6 @@ class AmneziaActivity : QtActivity(), LifecycleOwner {
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")
fun startQrCodeReader() {
Log.v(TAG, "Start camera")
@@ -1030,11 +1009,9 @@ class AmneziaActivity : QtActivity(), LifecycleOwner {
fun startPairingQrCodeReader() {
val now = SystemClock.uptimeMillis()
if (now - lastPairingQrReaderStartUptimeMs < 1200L) {
Log.w(TAG, "startPairingQrCodeReader: suppressed duplicate (${now - lastPairingQrReaderStartUptimeMs}ms)")
return
}
lastPairingQrReaderStartUptimeMs = now
Log.v(TAG, "Start pairing QR camera")
Intent(this, CameraActivity::class.java).also {
it.putExtra(CameraActivity.EXTRA_PAIRING_QR_CAMERA, true)
startActivity(it)
@@ -170,7 +170,6 @@ class CameraActivity : ComponentActivity() {
if (!::viewBinding.isInitialized) {
return
}
Log.v(TAG, "onNewIntent: rebind pairing camera")
cleanupCameraResources()
qrHandledOrClosing.set(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");
}
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)
{
callActivityMethod("setSaveLogs", "(Z)V", enabled);
@@ -47,9 +47,6 @@ public:
int getNavigationBarHeight();
void startQrReaderActivity();
void startPairingQrReaderActivity();
void startPairingQrEmbeddedCamera();
void stopPairingQrEmbeddedCamera();
void setPairingQrEmbeddedTorch(bool enabled);
void setSaveLogs(bool enabled);
void exportLogsFile(const QString &fileName);
void clearLogs();
+2 -90
View File
@@ -4,51 +4,15 @@
#include "platforms/ios/iosPairingCameraAccess.h"
#include <QByteArray>
#include <QDebug>
#include <QThread>
#import <UIKit/UIKit.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)
{
UIApplication *app = [UIApplication sharedApplication];
NSMutableArray<NSString *> *trace = [NSMutableArray array];
if (@available(iOS 13.0, *)) {
NSInteger sceneCount = app.connectedScenes.count;
[trace addObject:[NSString stringWithFormat:@"connectedScenes=%ld", (long)sceneCount]];
for (UIScene *scene in app.connectedScenes) {
if (scene.activationState != UISceneActivationStateForegroundActive) {
continue;
@@ -57,17 +21,13 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
continue;
}
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) {
if (window.isKeyWindow) {
NSLog(@"[QRCodeReader] keyWindow pick: scene keyWindow=%@ bounds=%@", window, NSStringFromCGRect(window.bounds));
return window;
}
}
for (UIWindow *window in windowScene.windows) {
if (!window.isHidden) {
NSLog(@"[QRCodeReader] keyWindow pick: scene nonHidden=%@ bounds=%@", window, NSStringFromCGRect(window.bounds));
return window;
}
}
@@ -75,25 +35,14 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
}
if (app.keyWindow) {
[trace addObject:@"app.keyWindow"];
NSLog(@"[QRCodeReader] keyWindow pick: application.keyWindow=%@ bounds=%@",
app.keyWindow, NSStringFromCGRect(app.keyWindow.bounds));
return app.keyWindow;
}
for (UIWindow *window in app.windows) {
if (window.isKeyWindow) {
NSLog(@"[QRCodeReader] keyWindow pick: windows scan key=%@ bounds=%@", window, NSStringFromCGRect(window.bounds));
return window;
}
}
UIWindow *first = 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;
return app.windows.firstObject;
}
@interface QRCodeReaderImpl : UIViewController
@@ -135,7 +84,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
if ([input isKindOfClass:[AVCaptureDeviceInput class]]) {
AVCaptureDevice *d = ((AVCaptureDeviceInput *)input).device;
if (d) {
NSLog(@"[QRCodeReader] resolvedCaptureDevice from session input device=%p", d);
return d;
}
}
@@ -155,13 +103,11 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
return;
}
if (![device hasTorch]) {
NSLog(@"[QRCodeReader] torch: device %p has no torch", device);
return;
}
AVCaptureSession *session = self.captureSession;
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(), ^{
if (on) {
[self applyTorchOnMainThread:YES];
@@ -183,8 +129,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
if ([device isTorchModeSupported:AVCaptureTorchModeOn]) {
device.torchMode = AVCaptureTorchModeOn;
}
} else {
NSLog(@"[QRCodeReader] torch ON ok level=maxAvailable");
}
} else {
device.torchMode = AVCaptureTorchModeOff;
@@ -193,7 +137,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
}
- (void)applyTorch:(BOOL)on {
NSLog(@"[QRCodeReader] applyTorch requested on=%d thread=%@", (int)on, amneziaQrThreadTag());
if ([NSThread isMainThread]) {
[self applyTorchOnMainThread:on];
} else {
@@ -204,9 +147,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
}
- (BOOL)startReadingOnMainThread {
NSLog(@"[QRCodeReader] startReadingOnMainThread begin thread=%@", amneziaQrThreadTag());
amneziaQrLogDeviceAuth();
[self stopReadingOnMainThread];
NSError *error = nil;
@@ -216,7 +156,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
NSLog(@"[QRCodeReader] defaultDeviceWithMediaType:Video is nil");
return NO;
}
NSLog(@"[QRCodeReader] capture device=%p localizedName=%@", captureDevice, captureDevice.localizedName);
AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
@@ -226,7 +165,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
}
self.activeCaptureDevice = captureDevice;
NSLog(@"[QRCodeReader] activeCaptureDevice set to %p", self.activeCaptureDevice);
AVCaptureSession *session = [[AVCaptureSession alloc] init];
[session addInput:deviceInput];
@@ -260,26 +198,16 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
self.videoPreviewPlayer.zPosition = -1000.f;
[keyWindow.layer insertSublayer:self.videoPreviewPlayer atIndex:0];
amneziaIosPairingRelayoutChromeIfNeeded();
NSLog(@"[QRCodeReader] previewLayer inserted window=%@ layer.sublayers.count=%lu bounds=%@",
keyWindow, (unsigned long)keyWindow.layer.sublayers.count, NSStringFromCGRect(bounds));
AVCaptureSession *runningSession = self.captureSession;
dispatch_async(_sessionQueue, ^{
NSLog(@"[QRCodeReader] session startRunning on session queue session=%p", runningSession);
[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;
}
- (BOOL)startReading {
NSLog(@"[QRCodeReader] startReading entry thread=%@ qt=%p", amneziaQrThreadTag(), (void *)QThread::currentThread());
if ([NSThread isMainThread]) {
return [self startReadingOnMainThread];
}
@@ -287,12 +215,10 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
dispatch_sync(dispatch_get_main_queue(), ^{
ok = [self startReadingOnMainThread];
});
NSLog(@"[QRCodeReader] startReading exit ok=%d (dispatched to main)", (int)ok);
return ok;
}
- (void)stopReadingOnMainThread {
NSLog(@"[QRCodeReader] stopReadingOnMainThread thread=%@", amneziaQrThreadTag());
[self applyTorchOnMainThread:NO];
self.activeCaptureDevice = nil;
@@ -311,7 +237,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
dispatch_sync(_sessionQueue, ^{
@try {
if ([session isRunning]) {
NSLog(@"[QRCodeReader] session stopRunning (sync) session=%p", session);
[session stopRunning];
}
} @catch (NSException *ex) {
@@ -321,14 +246,12 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
}
if (self.videoPreviewPlayer) {
NSLog(@"[QRCodeReader] remove preview from superlayer");
[self.videoPreviewPlayer removeFromSuperlayer];
self.videoPreviewPlayer = nil;
}
}
- (void)stopReading {
NSLog(@"[QRCodeReader] stopReading entry thread=%@ qt=%p", amneziaQrThreadTag(), (void *)QThread::currentThread());
if ([NSThread isMainThread]) {
[self stopReadingOnMainThread];
} else {
@@ -336,7 +259,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
[self stopReadingOnMainThread];
});
}
NSLog(@"[QRCodeReader] stopReading exit");
}
- (void)captureOutput:(AVCaptureOutput *)output
@@ -351,7 +273,6 @@ static UIWindow *amneziaKeyWindowForQrCamera(void)
if (value.length == 0) {
return;
}
NSLog(@"[QRCodeReader] metadata QR len=%lu", static_cast<unsigned long>(value.length));
QRCodeReader *cpp = _qrCodeReader;
const QByteArray utf8([value UTF8String]);
dispatch_async(dispatch_get_main_queue(), ^{
@@ -374,21 +295,13 @@ QRect QRCodeReader::cameraSize() {
void QRCodeReader::setCameraSize(QRect value) {
m_cameraSize = value;
qInfo() << "[QRCodeReader] setCameraSize" << value;
}
void QRCodeReader::startReading() {
qInfo() << "[QRCodeReader] C++ startReading thread" << QThread::currentThread();
const BOOL ok = [m_qrCodeReader startReading];
if (!ok) {
qWarning() << "[QRCodeReader] C++ startReading failed (see NSLogs)";
} else {
qInfo() << "[QRCodeReader] C++ startReading ok";
}
[m_qrCodeReader startReading];
}
void QRCodeReader::stopReading() {
qInfo() << "[QRCodeReader] C++ stopReading thread" << QThread::currentThread();
[m_qrCodeReader stopReading];
}
@@ -397,7 +310,6 @@ void QRCodeReader::notifyCodeRead(const QString &code) {
}
void QRCodeReader::setTorchEnabled(bool on) {
qInfo() << "[QRCodeReader] C++ setTorchEnabled" << on << "thread" << QThread::currentThread();
[(QRCodeReaderImpl *)m_qrCodeReader applyTorch:on ? YES : NO];
}
#else
@@ -130,11 +130,9 @@ static void amneziaLayoutPairingSafeAreaDimStrips(void)
CGFloat bottomY = hb.size.height - insets.bottom;
CGFloat bottomH = insets.bottom;
UIView *qt = s_pairingDimQtRoot;
CGFloat qMaxYForLog = hb.size.height;
if (qt && qt.superview) {
CGRect qInHost = [host convertRect:qt.bounds fromView:qt];
const CGFloat qMaxY = CGRectGetMaxY(qInHost);
qMaxYForLog = qMaxY;
if (qMaxY < hb.size.height - 0.5f) {
bottomY = qMaxY;
bottomH = hb.size.height - qMaxY;
@@ -168,28 +166,6 @@ static void amneziaLayoutPairingSafeAreaDimStrips(void)
s_pairingSafeBottomDim.frame = CGRectMake(0, bottomY, hb.size.width, bottomH);
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)
@@ -227,10 +203,6 @@ static void amneziaInstallPairingSafeAreaDimStrips(UIWindow *window, UIView *qtR
[host insertSubview:s_pairingSafeTopDim 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]
addObserverForName:UIDeviceOrientationDidChangeNotification
object:nil
@@ -277,7 +249,6 @@ static void amneziaForceMetalViewsTransparent(UIView *view, NSUInteger depth, NS
if (view.layer) {
view.layer.backgroundColor = [UIColor clearColor].CGColor;
}
NSLog(@"[PairingCamera] forceMetalTransparent depth=%lu class=%@", (unsigned long)depth, cn);
}
if (depth < maxDepth) {
for (UIView *child in view.subviews) {
@@ -358,7 +329,6 @@ void amneziaIosApplyEmbeddedCameraUnderlayToQtView(bool enable)
void (^work)(void) = ^{
UIViewController *vc = amneziaKeyWindowViewController();
if (!vc || !vc.view) {
NSLog(@"[PairingCamera] amneziaIosApplyEmbeddedCameraUnderlayToQtView: no root VC (enable=%d)", (int)enable);
return;
}
UIView *root = vc.view;
@@ -378,9 +348,6 @@ void amneziaIosApplyEmbeddedCameraUnderlayToQtView(bool enable)
amneziaInstallPairingSafeAreaDimStrips(win, root);
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) {
dispatch_async(dispatch_get_main_queue(), ^{
UIViewController *vc2 = amneziaKeyWindowViewController();
@@ -388,7 +355,6 @@ void amneziaIosApplyEmbeddedCameraUnderlayToQtView(bool enable)
return;
}
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). */
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)
{
for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
@@ -113,11 +63,9 @@ static CGFloat amneziaPairingQrBottomTabStripReserve(UIWindowScene *scene)
if ([cw.rootViewController isKindOfClass:qios]) {
const CGFloat inset = cw.safeAreaInsets.bottom;
const CGFloat reserve = inset + 49.f;
NSLog(@"[PairingQrOverlay] bottomReserve: QUIWindow safeBottom=%.1f -> reserve=%.1f", inset, reserve);
return MIN(MAX(reserve, 72.f), 140.f);
}
}
NSLog(@"[PairingQrOverlay] bottomReserve: QUIWindow not found, default 83");
return 83.f;
}
@@ -548,7 +496,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
- (void)backTapped
{
NSLog(@"[PairingQrOverlay] native back tapped");
if (gOnBack) {
gOnBack();
}
@@ -557,7 +504,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
- (void)torchTapped
{
gTorchRequested = !gTorchRequested;
NSLog(@"[PairingQrOverlay] native torch toggle -> %d", (int)gTorchRequested);
[self applyTorchFromGlobalFlag];
if (gTorchRequested) {
self.torchButton.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.42];
@@ -597,8 +543,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
{
AVCaptureDevice *device = self.videoDevice;
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) {
__unsafe_unretained AmneziaPairingQrOverlayViewController *unsafeSelf = self;
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
{
NSLog(@"[PairingQrOverlay] startCapturePipelineOnMainThread entry camera.bounds=%@ thread=%@",
self.cameraContainer ? NSStringFromCGRect(self.cameraContainer.bounds) : @"(nil)",
[NSThread isMainThread] ? @"main" : @"bg");
[self stopCapturePipelineOnMainThread];
@@ -693,8 +634,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
NSLog(@"[PairingQrOverlay] no default video device");
return NO;
}
NSLog(@"[PairingQrOverlay] capture device=%p modelID=%@ localizedName=%@", (void *)device, device.modelID,
device.localizedName);
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
if (!input) {
NSLog(@"[PairingQrOverlay] deviceInput failed: %@", error.localizedDescription);
@@ -706,7 +645,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
if ([session canSetSessionPreset:AVCaptureSessionPresetHigh]) {
session.sessionPreset = AVCaptureSessionPresetHigh;
}
NSLog(@"[PairingQrOverlay] session preset=%@", session.sessionPreset);
[session addInput:input];
@@ -732,7 +670,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
self.previewLayer = preview;
[self.cameraContainer.layer insertSublayer:preview atIndex:0];
preview.frame = self.cameraContainer.bounds;
NSLog(@"[PairingQrOverlay] previewLayer on host frame=%@", NSStringFromCGRect(preview.frame));
[self.view layoutIfNeeded];
[self layoutScanOverlayGeometry];
@@ -740,22 +677,18 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
AVCaptureSession *runningSession = session;
__unsafe_unretained AmneziaPairingQrOverlayViewController *weakSelf = self;
dispatch_async(q, ^{
NSLog(@"[PairingQrOverlay] session queue: startRunning begin session=%p", (void *)runningSession);
@try {
[runningSession startRunning];
} @catch (NSException *ex) {
NSLog(@"[PairingQrOverlay] startRunning exception: %@", ex);
}
const BOOL running = [runningSession isRunning];
NSLog(@"[PairingQrOverlay] session queue: startRunning end isRunning=%d", (int)running);
dispatch_async(dispatch_get_main_queue(), ^{
AmneziaPairingQrOverlayViewController *strongSelf = weakSelf;
if (!strongSelf) {
return;
}
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];
});
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;
}
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(), ^{
if (gOnScanned) {
NSLog(@"[PairingQrOverlay] metadata QR len=%lu", (unsigned long)copy.size());
gOnScanned(copy.c_str());
} else {
NSLog(@"[PairingQrOverlay] metadata QR but gOnScanned is nil");
}
});
break;
@@ -805,7 +733,6 @@ static UIBezierPath *amneziaScanBracketStrokePath(int corner, CGFloat x0, CGFloa
static void amneziaPairingQrOverlayTeardownOnMain(void)
{
NSLog(@"[PairingQrOverlay] teardownOnMain overlayPtr=%p", (void *)gPairingQrOverlayWindow);
UIWindow *w = gPairingQrOverlayWindow;
gPairingQrOverlayWindow = nil;
gOnScanned = nullptr;
@@ -824,12 +751,9 @@ static void amneziaPairingQrOverlayTeardownOnMain(void)
UIWindow *restore = amneziaPickQtAppWindowToRestore();
if (restore) {
NSLog(@"[PairingQrOverlay] teardown: restore keyWindow to %@", restore);
[restore makeKeyWindow];
} else {
NSLog(@"[PairingQrOverlay] teardown: no window to restore as key");
}
amneziaPairingQrLogWindows(@"afterTeardown");
}
void amneziaIosPairingQrOverlayPresent(AmneziaPairingQrScannedUtf8Handler onScanned, AmneziaPairingQrOverlayBackHandler onBack,
@@ -837,26 +761,20 @@ void amneziaIosPairingQrOverlayPresent(AmneziaPairingQrScannedUtf8Handler onScan
{
const bool hasScan = static_cast<bool>(onScanned);
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);
AmneziaPairingQrOverlayBackHandler backH = std::move(onBack);
const std::string titleCopy = titleUtf8;
const std::string subCopy = subtitleUtf8;
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"[PairingQrOverlay] present block on main");
amneziaPairingQrLogScenes(@"beforeTeardown");
amneziaPairingQrLogWindows(@"beforeTeardown");
amneziaPairingQrOverlayTeardownOnMain();
gOnScanned = std::move(scanH);
gOnBack = std::move(backH);
NSLog(@"[PairingQrOverlay] callbacks stored scan=%d back=%d", (int)(bool)gOnScanned, (int)(bool)gOnBack);
UIWindowScene *scene = amneziaForegroundWindowScene();
if (!scene) {
NSLog(@"[PairingQrOverlay] present: no UIWindowScene");
amneziaPairingQrLogScenes(@"sceneNil");
gOnScanned = nullptr;
gOnBack = nullptr;
return;
@@ -878,28 +796,22 @@ void amneziaIosPairingQrOverlayPresent(AmneziaPairingQrScannedUtf8Handler onScan
w.backgroundColor = [UIColor blackColor];
w.rootViewController = vc;
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 layoutIfNeeded];
[vc.view setNeedsLayout];
[vc.view layoutIfNeeded];
NSLog(@"[PairingQrOverlay] after layout vc.view.bounds=%@ window.key=%d", NSStringFromCGRect(vc.view.bounds),
(int)w.isKeyWindow);
gPairingQrOverlayKeySince = CFAbsoluteTimeGetCurrent();
if (![vc startCapturePipelineOnMainThread]) {
NSLog(@"[PairingQrOverlay] startCapture failed");
}
amneziaPairingQrLogWindows(@"afterPresent");
});
}
void amneziaIosPairingQrOverlayDismiss()
{
NSLog(@"[PairingQrOverlay] C++ dismiss requested");
dispatch_async(dispatch_get_main_queue(), ^{
amneziaPairingQrOverlayTeardownOnMain();
});
@@ -907,12 +819,10 @@ void amneziaIosPairingQrOverlayDismiss()
void amneziaIosPairingQrOverlaySetTorchEnabled(bool on)
{
NSLog(@"[PairingQrOverlay] C++ setTorch=%d", (int)on);
gTorchRequested = on;
dispatch_async(dispatch_get_main_queue(), ^{
UIWindow *win = gPairingQrOverlayWindow;
if (!win) {
NSLog(@"[PairingQrOverlay] setTorch: no overlay window");
return;
}
UIViewController *root = win.rootViewController;
@@ -935,25 +845,20 @@ void amneziaIosPairingQrOverlaySetTorchEnabled(bool on)
void amneziaIosPairingQrOverlayRestartCapture()
{
NSLog(@"[PairingQrOverlay] C++ restartCapture requested");
dispatch_async(dispatch_get_main_queue(), ^{
const CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
if (gPairingQrOverlayKeySince > 0 && (now - gPairingQrOverlayKeySince) < 1.0) {
NSLog(@"[PairingQrOverlay] restartCapture: skipped warm-up (%.3fs since overlay key)", now - gPairingQrOverlayKeySince);
return;
}
UIWindow *w = gPairingQrOverlayWindow;
if (!w) {
NSLog(@"[PairingQrOverlay] restartCapture: no overlay window");
return;
}
UIViewController *root = w.rootViewController;
if (![root isKindOfClass:[AmneziaPairingQrOverlayViewController class]]) {
NSLog(@"[PairingQrOverlay] restartCapture: rootVC class=%@", root ? NSStringFromClass(root.class) : @"(nil)");
return;
}
AmneziaPairingQrOverlayViewController *vc = (AmneziaPairingQrOverlayViewController *)root;
NSLog(@"[PairingQrOverlay] restartCapture: stop+start");
[vc stopCapturePipelineOnMainThread];
if (![vc startCapturePipelineOnMainThread]) {
NSLog(@"[PairingQrOverlay] restart startCapture failed");
@@ -3,7 +3,6 @@
#include <QCoreApplication>
#include <QDataStream>
#include <QDateTime>
#include <QDebug>
#include <QIODevice>
#include <QJsonArray>
#include <QJsonObject>
@@ -241,7 +240,6 @@ void PairingUiController::clearPendingPhonePairingUuid()
void PairingUiController::openPairingQrScanner()
{
#if defined(Q_OS_ANDROID)
qInfo() << "[PairingUi] openPairingQrScanner (Android native activity)";
AndroidController::instance()->startPairingQrReaderActivity();
#endif
}
@@ -251,9 +249,7 @@ bool PairingUiController::isPairingCameraAccessGranted() const
#if defined(Q_OS_ANDROID)
return AndroidController::instance()->isCameraPermissionGranted();
#elif defined(Q_OS_IOS)
const bool ok = amneziaIosPairingCameraAccessGranted();
qInfo() << "[PairingUi] iOS isPairingCameraAccessGranted =" << ok;
return ok;
return amneziaIosPairingCameraAccessGranted();
#else
return true;
#endif
@@ -265,7 +261,6 @@ void PairingUiController::requestPairingCameraAccess()
AndroidController::instance()->requestCameraPermissionForQrPairing();
#elif defined(Q_OS_IOS)
amneziaIosRequestPairingCameraAccess([this](bool granted) {
qInfo() << "[PairingUi] iOS requestPairingCameraAccess callback granted =" << granted;
QMetaObject::invokeMethod(
this, [this, granted]() { emit pairingCameraAccessFinished(granted); }, Qt::QueuedConnection);
});
@@ -283,52 +278,10 @@ void PairingUiController::openPairingCameraAppSettings()
#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)
{
#if defined(Q_OS_ANDROID)
AndroidController::instance()->setPairingQrEmbeddedTorch(enabled);
Q_UNUSED(enabled);
#elif defined(Q_OS_IOS)
amneziaIosPairingQrOverlaySetTorchEnabled(enabled);
#else
@@ -339,7 +292,6 @@ void PairingUiController::setPairingQrTorchEnabled(bool enabled)
void PairingUiController::presentIosPairingQrNativeOverlayScanner(const QString &title, const QString &subtitle)
{
#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 subtitleUtf8 = subtitle.isEmpty() ? std::string() : subtitle.toStdString();
amneziaIosPairingQrOverlayPresent(
@@ -364,14 +316,12 @@ void PairingUiController::presentIosPairingQrNativeOverlayScanner(const QString
#else
Q_UNUSED(title);
Q_UNUSED(subtitle);
qInfo() << "[PairingUi] presentIosPairingQrNativeOverlayScanner: no-op (not iOS build)";
#endif
}
void PairingUiController::dismissIosPairingQrNativeOverlayScanner()
{
#if defined(Q_OS_IOS)
qInfo() << "[PairingUi] dismissIosPairingQrNativeOverlayScanner";
amneziaIosPairingQrOverlayDismiss();
#endif
}
@@ -379,25 +329,17 @@ void PairingUiController::dismissIosPairingQrNativeOverlayScanner()
void PairingUiController::restartIosPairingQrNativeOverlayCapture()
{
#if defined(Q_OS_IOS)
qInfo() << "[PairingUi] restartIosPairingQrNativeOverlayCapture";
amneziaIosPairingQrOverlayRestartCapture();
#endif
}
bool PairingUiController::applyScannedTextAsPairingUuid(const QString &raw)
{
const QString t = raw.trimmed();
qInfo() << "[PairingUi] scan raw len=" << t.size();
const QString uuid = extractPairingSessionUuidFromScanText(raw);
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;
}
qInfo() << "[PairingUi] scan accepted uuid=" << uuid.left(13) << "...";
emit pairingUuidFromScan(uuid);
return true;
}
@@ -406,7 +348,6 @@ bool PairingUiController::applyScannedTextAsPairingUuid(const QString &raw)
bool PairingUiController::tryConsumeAndroidQrScan(const QString &code)
{
if (!g_pairingUiForAndroidQr) {
qWarning() << "[PairingUi] tryConsumeAndroidQrScan: no controller (g_pairingUiForAndroidQr null)";
return false;
}
const QString codeCopy = code;
@@ -423,7 +364,6 @@ bool PairingUiController::tryConsumeAndroidQrScan(const QString &code)
}
ctlPtr->applyScannedTextAsPairingUuid(codeCopy);
});
qInfo() << "[PairingUi] tryConsumeAndroidQrScan: scheduled apply on Qt thread, rawLen=" << codeCopy.size();
return true;
}
@@ -743,8 +683,6 @@ void PairingUiController::cancelAllPairingActivity()
}
cancelTvQrSession();
setEmbeddedPairingQrCameraActive(false);
}
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();
qInfo() << "[PairingUi] submitPhonePairing serverIndex=" << serverIndex << "uuidLen=" << trimmedUuid.size();
if (trimmedUuid.isEmpty()) {
qWarning() << "[PairingUi] submitPhonePairing aborted: empty UUID (paste or scan first)";
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
return;
}
if (serverIndex < 0 || serverIndex >= m_serversController->getServersCount()) {
qWarning() << "[PairingUi] submitPhonePairing invalid serverIndex";
emit errorOccurred(ErrorCode::InternalError);
return;
}
@@ -773,7 +708,6 @@ void PairingUiController::submitPhonePairing(const QString &qrUuid, int serverIn
const QString serverId = m_serversController->getServerId(serverIndex);
const auto apiV2Opt = m_serversController->apiV2Config(serverId);
if (!apiV2Opt.has_value()) {
qWarning() << "[PairingUi] submitPhonePairing server is not API v2";
emit errorOccurred(ErrorCode::InternalError);
return;
}
@@ -783,7 +717,6 @@ void PairingUiController::submitPhonePairing(const QString &qrUuid, int serverIn
QString vpnKey;
const ErrorCode keyErr = m_subscriptionController->prepareVpnKeyExport(serverId, vpnKey);
if (keyErr != ErrorCode::NoError) {
qWarning() << "[PairingUi] prepareVpnKeyExport failed" << static_cast<int>(keyErr);
emit errorOccurred(keyErr);
return;
}
@@ -792,7 +725,6 @@ void PairingUiController::submitPhonePairing(const QString &qrUuid, int serverIn
const QJsonArray supportedProtocols = apiV2.apiConfig.supportedProtocols;
const QString apiKey = apiV2.authData.apiKey;
if (apiKey.isEmpty()) {
qWarning() << "[PairingUi] submitPhonePairing aborted: empty API key on server card";
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
return;
}
@@ -32,8 +32,6 @@ class PairingUiController : public QObject
pendingPhonePairingUuidChanged)
Q_PROPERTY(QString lastSuccessfulPhonePairingDisplayName READ lastSuccessfulPhonePairingDisplayName NOTIFY
lastSuccessfulPhonePairingDisplayNameChanged)
Q_PROPERTY(bool embeddedPairingQrCameraActive READ embeddedPairingQrCameraActive WRITE setEmbeddedPairingQrCameraActive NOTIFY
embeddedPairingQrCameraActiveChanged)
Q_PROPERTY(bool iosNativePairingQrOverlayBuild READ iosNativePairingQrOverlayBuild CONSTANT)
Q_PROPERTY(bool androidNativePairingQrOverlayBuild READ androidNativePairingQrOverlayBuild CONSTANT)
Q_PROPERTY(qint64 androidPairingReaderCooldownUntilEpochMs READ androidPairingReaderCooldownUntilEpochMs NOTIFY
@@ -57,12 +55,8 @@ public:
QString pendingPhonePairingUuid() const { return m_pendingPhonePairingUuid; }
void setPendingPhonePairingUuid(const QString &uuid);
QString lastSuccessfulPhonePairingDisplayName() const { return m_lastSuccessfulPhonePairingDisplayName; }
bool embeddedPairingQrCameraActive() const { return m_embeddedPairingQrCameraActive; }
bool iosNativePairingQrOverlayBuild() 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; }
@@ -110,7 +104,6 @@ signals:
void pairingUuidFromScan(const QString &uuid);
void pairingCameraAccessFinished(bool granted);
void embeddedPairingQrCameraActiveChanged();
void androidPairingReaderCooldownUntilEpochMsChanged();
void pairingSendQrScanRejectedInvalidPayload();
void pairingIosNativeQrOverlayBackRequested();
@@ -149,7 +142,6 @@ private:
QPointer<QNetworkReply> m_phoneNetworkReply;
quint64 m_phoneSessionGeneration { 0 };
bool m_embeddedPairingQrCameraActive = false;
qint64 m_androidPairingReaderCooldownUntilEpochMs = 0;
};
@@ -82,7 +82,6 @@ namespace PageLoader
PageSetupWizardApiPremiumInfo,
PageSetupWizardApiTrialEmail,
PageSettingsApiQrPairingDev,
PageSettingsApiQrPairingSend,
PageSetupWizardApiQrPairingReceive,
+1 -1
View File
@@ -4,7 +4,7 @@ import QtQuick.Controls
Menu {
property var textObj
popupType: Popup.Native
popupType: Qt.platform.os === "ios" ? Popup.Item : Popup.Native
onAboutToShow: blocker.enabled = true
onClosed: blocker.enabled = false
@@ -53,10 +53,7 @@ TabButton {
background: Rectangle {
id: background
anchors.fill: parent
/** iOS embedded QR camera: clear window + preview under Qt; transparent tab cells show the camera. */
color: (PairingUiController.embeddedPairingQrCameraActive && Qt.platform.os !== "android")
? AmneziaStyle.color.onyxBlack
: AmneziaStyle.color.transparent
color: AmneziaStyle.color.transparent
radius: 10
border.color: root.activeFocus ? root.borderFocusedColor : AmneziaStyle.color.transparent
-15
View File
@@ -90,21 +90,6 @@ PageType {
footer: ColumnLayout {
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 {
Layout.fillWidth: true
Layout.topMargin: 16
@@ -109,11 +109,11 @@ PageType {
}
function onPhonePairingSucceeded() {
const serverIndex = ServersUiController.getProcessedServerIndex()
if (serverIndex < 0) {
var serverId = ServersUiController.getServerId(ServersUiController.processedServerIndex)
if (serverId.length === 0) {
return
}
SubscriptionUiController.getAccountInfo(serverIndex, true)
SubscriptionUiController.getAccountInfo(serverId, true)
SubscriptionUiController.updateApiDevicesModel()
if (!root.visible) {
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.Window
import QtQuick.Controls
import QtQuick.Layouts
import QRCodeReader 1.0
import PageEnum 1.0
import Style 1.0
@@ -16,71 +14,12 @@ import "../Components"
PageType {
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 useAndroidNativePairingQrScanner: GC.isMobile() && Qt.platform.os === "android"
readonly property bool useAndroidNativePairingQrOverlay: PairingUiController.androidNativePairingQrOverlayBuild
&& GC.isMobile()
&& 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 bool keepPhonePairingInBackgroundOnClose: false
@@ -88,31 +27,17 @@ PageType {
property bool addDeviceConfirmNavigationScheduled: false
property bool awaitingCameraPermissionForScan: false
property bool waitingSettingsReturnForScan: false
property bool torchOn: false
/** Suppress double startActivity when StackView fires both Component.onCompleted and onVisibleChanged. */
property int _androidPairingReaderLastStartMs: 0
Timer {
id: pairingCameraKickTimer
interval: 220
repeat: false
onTriggered: root.restartPairingIosCamera()
}
function stopMobileScanner() {
torchOn = false
if (root.useIosNativePairingQrOverlay) {
PairingUiController.setPairingQrTorchEnabled(false)
PairingUiController.dismissIosPairingQrNativeOverlayScanner()
return
}
if (Qt.platform.os === "android") {
} else if (Qt.platform.os === "android") {
PairingUiController.setPairingQrTorchEnabled(false)
} else if (root.useIosStyleNativeQrReader) {
pairingQrReader.setTorchEnabled(false)
}
pairingQrReader.stopReading()
PairingUiController.embeddedPairingQrCameraActive = false
}
function startMobileScanner() {
@@ -122,7 +47,6 @@ PageType {
if (!root.visible) {
return
}
/** Confirm step (or transition to it): never reopen native / embedded scanner from stray taps or visibility. */
if (root.pairingWizardStep !== 0) {
return
}
@@ -138,10 +62,9 @@ PageType {
PairingUiController.presentIosPairingQrNativeOverlayScanner(
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."))
/** Do not run pairingCameraKickTimer here: restartCapture during first startRunning races the session (torch needs 23 taps). */
return
}
if (root.useAndroidNativePairingQrScanner) {
if (Qt.platform.os === "android") {
const coolUntil = PairingUiController.androidPairingReaderCooldownUntilEpochMs
if (Date.now() < coolUntil) {
return
@@ -152,23 +75,7 @@ PageType {
}
_androidPairingReaderLastStartMs = now
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() {
@@ -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: {
if (!keepPhonePairingInBackgroundOnClose && !PairingUiController.phonePairingBusy) {
PairingUiController.cancelAllPairingActivity()
@@ -217,18 +110,16 @@ PageType {
onVisibleChanged: {
if (visible) {
/** Only reset confirm flag on scan step; clearing it on confirm breaks guards if visible flickers. */
if (pairingWizardStep === 0) {
addDeviceConfirmNavigationScheduled = false
Qt.callLater(startMobileScanner)
}
} else {
pairingCameraKickTimer.stop()
} else if (!PairingUiController.phonePairingBusy) {
stopMobileScanner()
_androidPairingReaderLastStartMs = 0
pairingWizardStep = 0
waitingSettingsReturnForScan = false
if (!keepPhonePairingInBackgroundOnClose && !PairingUiController.phonePairingBusy) {
if (!keepPhonePairingInBackgroundOnClose) {
PairingUiController.cancelAllPairingActivity()
}
}
@@ -238,16 +129,12 @@ PageType {
if (pairingWizardStep !== 0) {
stopMobileScanner()
} 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)
}
}
Component.onCompleted: {
if (GC.isMobile() && root.visible && pairingWizardStep === 0) {
if (visible && pairingWizardStep === 0) {
Qt.callLater(startMobileScanner)
}
}
@@ -260,37 +147,15 @@ PageType {
return
}
root.tryResumeScanAfterCameraSettings()
if (!root.useIosStyleNativeQrReader || root.pairingWizardStep !== 0
|| !PairingUiController.isPairingCameraAccessGranted()) {
return
}
if (root.useIosNativePairingQrOverlay) {
Qt.callLater(function () {
if (!root.visible || root.pairingWizardStep !== 0 || !GC.isMobile()) {
return
}
PairingUiController.restartIosPairingQrNativeOverlayCapture()
})
if (!root.useIosNativePairingQrOverlay || root.pairingWizardStep !== 0
|| !PairingUiController.isPairingCameraAccessGranted()) {
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()
})
})
PairingUiController.restartIosPairingQrNativeOverlayCapture()
})
}
}
@@ -308,15 +173,15 @@ PageType {
Item {
anchors.fill: parent
/** Brief Qt backdrop + back while CameraActivity is starting (native holds title/instructions like iOS overlay). */
Rectangle {
anchors.fill: parent
visible: pairingWizardStep === 0 && root.useAndroidNativePairingQrOverlay
color: AmneziaStyle.color.midnightBlack
z: 1
}
BackButtonType {
visible: pairingWizardStep === 0 && root.useAndroidNativePairingQrOverlay
visible: pairingWizardStep === 0 && (root.useAndroidNativePairingQrOverlay || !GC.isMobile())
anchors.top: parent.top
anchors.topMargin: PageController.safeAreaTopMargin
anchors.left: parent.left
@@ -327,312 +192,25 @@ PageType {
}
}
Item {
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()
}
Column {
anchors.centerIn: parent
width: parent.width - 48
spacing: 12
visible: pairingWizardStep === 0 && !GC.isMobile()
Label {
anchors.centerIn: parent
width: parent.width - 48
width: parent.width
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
visible: !GC.isMobile()
color: AmneziaStyle.color.mutedGray
font.pixelSize: 15
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 {
id: confirmStep
anchors.fill: parent
anchors.leftMargin: 0
anchors.rightMargin: 0
visible: pairingWizardStep === 1
z: 10
spacing: 16
@@ -732,7 +310,6 @@ PageType {
addDeviceConfirmNavigationScheduled = true
stopMobileScanner()
PairingUiController.pendingPhonePairingUuid = uuid
/** Immediate step switch so scan chrome is not hit-testable for another frame (avoids reopening CameraActivity). */
pairingWizardStep = 1
}
@@ -753,7 +330,6 @@ PageType {
PageController.closePage()
}
/** Native CameraActivity back: leave pairing flow (same as iOS overlay back). Do NOT reopen scanner. */
function onPairingAndroidNativeQrScannerUserDismissed() {
if (!root.useAndroidNativePairingQrOverlay) {
return
@@ -12,7 +12,6 @@ import "../Components"
PageType {
id: root
property int qrImageIndex: 0
readonly property int qrRefreshIntervalMs: Math.max(5000, PairingUiController.tvPairingWaitWindowSeconds * 1000)
function scrollPairingToBottom() {
@@ -134,15 +133,7 @@ PageType {
anchors.margins: 20
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
}
}
source: PairingUiController.tvQrCodesCount > 0 ? PairingUiController.tvQrCodes[0] : ""
}
}
}
@@ -170,7 +161,6 @@ PageType {
target: PairingUiController
function onTvQrCodesChanged() {
root.qrImageIndex = 0
if (PairingUiController.tvQrCodesCount > 0) {
scrollToBottomRetryTimer.retries = 0
scrollToBottomRetryTimer.start()
@@ -185,10 +175,6 @@ PageType {
}
}
function onTvSessionUuidChanged() {
root.qrImageIndex = 0
}
function onTvPairingConfigReceived() {
scrollToBottomRetryTimer.stop()
qrRotationTimer.stop()
+4 -13
View File
@@ -19,15 +19,6 @@ PageType {
property bool isControlsDisabled: 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 {
objectName: "pageControllerConnection"
@@ -279,7 +270,7 @@ PageType {
anchors.right: parent.right
anchors.left: parent.left
anchors.bottom: tabBar.top
anchors.bottomMargin: -root.tabStackPairingUnderlapDown
anchors.bottomMargin: 0
enabled: !root.isControlsDisabled
@@ -351,16 +342,16 @@ PageType {
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
height: parent.height + root.tabBarChromeOverlapUp
height: parent.height
Rectangle {
anchors.fill: parent
color: root.pairingQrChromeDebug ? "#00ff66" : AmneziaStyle.color.onyxBlack
color: AmneziaStyle.color.onyxBlack
}
Shape {
id: tabBarChromeShape
objectName: "backgroundShape"
visible: root.tabBarChromeOverlapUp === 0
visible: true
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
+1 -2
View File
@@ -16,8 +16,7 @@ Window {
id: root
objectName: "mainWindow"
readonly property bool pairingQrCameraUnderlay: PairingUiController.embeddedPairingQrCameraActive && GC.isMobile()
color: pairingQrCameraUnderlay ? "#00000000" : AmneziaStyle.color.midnightBlack
color: AmneziaStyle.color.midnightBlack
Connections {
target: Qt.application
-1
View File
@@ -97,7 +97,6 @@
<file>Pages2/PageSettingsAbout.qml</file>
<file>Pages2/PageSettingsApiAvailableCountries.qml</file>
<file>Pages2/PageSettingsApiServerInfo.qml</file>
<file>Pages2/PageSettingsApiQrPairingDev.qml</file>
<file>Pages2/PageSettingsApiQrPairingSend.qml</file>
<file>Pages2/PageSetupWizardApiQrPairingReceive.qml</file>
<file>Pages2/PageSettingsApplication.qml</file>