From 3082863fd193f82127da9379462e6b972141621f Mon Sep 17 00:00:00 2001 From: Eric Kuck Date: Tue, 21 Aug 2018 11:00:41 -0500 Subject: [PATCH] QR code: Ability to add tunnels with a QR code scan Signed-off-by: Eric Kuck --- .../UI/iOS/QRScanViewController.swift | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 WireGuard/WireGuard/UI/iOS/QRScanViewController.swift diff --git a/WireGuard/WireGuard/UI/iOS/QRScanViewController.swift b/WireGuard/WireGuard/UI/iOS/QRScanViewController.swift new file mode 100644 index 0000000..f15d30b --- /dev/null +++ b/WireGuard/WireGuard/UI/iOS/QRScanViewController.swift @@ -0,0 +1,107 @@ +// +// QRScanViewController.swift +// WireGuard +// +// Created by Eric Kuck on 8/20/18. +// Copyright © 2018 Jason A. Donenfeld . All rights reserved. +// + +import AVFoundation +import CoreData +import UIKit + +protocol QRScanViewControllerDelegate: class { + func didSave(tunnel: Tunnel, qrScanViewController: QRScanViewController) +} + +class QRScanViewController: UIViewController { + + private var viewContext: NSManagedObjectContext! + private weak var delegate: QRScanViewControllerDelegate? + var captureSession: AVCaptureSession? = AVCaptureSession() + let metadataOutput = AVCaptureMetadataOutput() + var previewLayer: AVCaptureVideoPreviewLayer! + + func configure(context: NSManagedObjectContext, delegate: QRScanViewControllerDelegate? = nil) { + viewContext = context + self.delegate = delegate + } + + override func viewDidLoad() { + super.viewDidLoad() + + guard let videoCaptureDevice = AVCaptureDevice.default(for: .video), + let videoInput = try? AVCaptureDeviceInput(device: videoCaptureDevice), + let captureSession = captureSession, + captureSession.canAddInput(videoInput), + captureSession.canAddOutput(metadataOutput) else { + scanDidEncounterError(title: "Scanning Not Supported", message: "This device does not have the ability to scan QR codes.") + return + } + + captureSession.addInput(videoInput) + captureSession.addOutput(metadataOutput) + + metadataOutput.setMetadataObjectsDelegate(self, queue: .main) + metadataOutput.metadataObjectTypes = [.qr] + + previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) + previewLayer.frame = view.layer.bounds + previewLayer.videoGravity = .resizeAspectFill + view.layer.addSublayer(previewLayer) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if captureSession?.isRunning == false { + captureSession?.startRunning() + } + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + if captureSession?.isRunning == true { + captureSession?.stopRunning() + } + } + + func scanDidComplete(withCode code: String) { + do { + let tunnel = try Tunnel.fromConfig(code, context: viewContext) + delegate?.didSave(tunnel: tunnel, qrScanViewController: self) + } catch { + scanDidEncounterError(title: "Invalid Code", message: "The scanned code is not a valid WireGuard config file.") + } + } + + func scanDidEncounterError(title: String, message: String) { + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { [weak self] _ in + self?.navigationController?.popViewController(animated: true) + })) + present(alertController, animated: true) + captureSession = nil + } + +} + +extension QRScanViewController: AVCaptureMetadataOutputObjectsDelegate { + func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) { + captureSession?.stopRunning() + + guard let metadataObject = metadataObjects.first, + let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject, + let stringValue = readableObject.stringValue else { + scanDidEncounterError(title: "Invalid Code", message: "The scanned code could not be read.") + return + } + + AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate)) + scanDidComplete(withCode: stringValue) + } + +} + +extension QRScanViewController: Identifyable {}