From 21119facf36081da4472ede4c187f31693eb411d Mon Sep 17 00:00:00 2001
From: Patrick Kranz <patrick-kranz@live.de>
Date: Sun, 9 May 2021 14:44:53 +0200
Subject: [PATCH 1/2] Added DeviceSettingsPage with QR reader

---
 Signal-Windows/Controls/QRScanner.cs          | 366 ++++++++++++++++++
 Signal-Windows/Controls/QRScanner.xaml        |  34 ++
 Signal-Windows/Package.appxmanifest           |   2 +
 Signal-Windows/Signal-Windows.csproj          |  15 +
 .../ViewModels/DeviceSettingsPageViewmodel.cs | 124 ++++++
 Signal-Windows/ViewModels/ViewModelLocator.cs |   6 +
 Signal-Windows/Views/DeviceSettingsPage.xaml  |  40 ++
 .../Views/DeviceSettingsPage.xaml.cs          |  96 +++++
 Signal-Windows/Views/GlobalSettingsPage.xaml  |   6 +
 .../Views/GlobalSettingsPage.xaml.cs          |   5 +
 10 files changed, 694 insertions(+)
 create mode 100644 Signal-Windows/Controls/QRScanner.cs
 create mode 100644 Signal-Windows/Controls/QRScanner.xaml
 create mode 100644 Signal-Windows/ViewModels/DeviceSettingsPageViewmodel.cs
 create mode 100644 Signal-Windows/Views/DeviceSettingsPage.xaml
 create mode 100644 Signal-Windows/Views/DeviceSettingsPage.xaml.cs

diff --git a/Signal-Windows/Controls/QRScanner.cs b/Signal-Windows/Controls/QRScanner.cs
new file mode 100644
index 0000000..4d362c1
--- /dev/null
+++ b/Signal-Windows/Controls/QRScanner.cs
@@ -0,0 +1,366 @@
+using System;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Media;
+
+using System.Threading;
+using Windows.Media.Capture;
+using System.Threading.Tasks;
+using Windows.Media;
+using Windows.Media.MediaProperties;
+using Windows.Graphics.Imaging;
+using Windows.Storage.Streams;
+using Windows.Media.Devices;
+using Windows.Devices.Enumeration;
+using ZXing;
+using libsignalservice;
+using Microsoft.Extensions.Logging;
+
+namespace Signal_Windows.Controls
+{
+
+    // this Control draw heavaly form the [Barcode_Scanner_UWP](https://github.com/programmersommer/Barcode_Scanner_UWP) sample.
+    public sealed partial class QRScanner : UserControl, IDisposable
+    {
+
+        private static readonly ILogger Logger = LibsignalLogging.CreateLogger<QRScanner>();
+
+        public event Action<string> CodeFound;
+        public event Action Cancled;
+        public event Action<Exception> Error;
+
+        private MediaCapture mediaCapture;
+        private readonly DispatcherTimer timerFocus;
+        private readonly SemaphoreSlim videoCaptureSemaphore = new SemaphoreSlim(1);
+        private readonly SemaphoreSlim scanSemaphore = new SemaphoreSlim(1);
+        private bool timerCaptureInProgress = false;
+
+        private double width = 640;
+        private double height = 480;
+        private bool isReady = true;
+        private bool isInitialized = false;
+        private bool isScanning = true;
+
+        private BarcodeReader _ZXingReader;
+        private bool disposedValue;
+
+        public QRScanner()
+        {
+            this.InitializeComponent();
+
+            this.timerFocus = new DispatcherTimer();
+        }
+
+        #region capturing photo
+
+        private async Task InitCamera()
+        {
+            if (this.isInitialized == true)
+                return;
+
+            var devices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
+
+            var rearCamera = devices.FirstOrDefault(x => x.EnclosureLocation?.Panel == Windows.Devices.Enumeration.Panel.Back);
+
+            try
+            {
+
+                if (rearCamera != null)
+                {
+                    await this.mediaCapture.InitializeAsync(new MediaCaptureInitializationSettings { VideoDeviceId = rearCamera.Id });
+                }
+
+                this.isInitialized = true;
+                await this.SetResolution();
+                if (this.mediaCapture.VideoDeviceController.FlashControl.Supported)
+                    this.mediaCapture.VideoDeviceController.FlashControl.Auto = false;
+            }
+            catch { }
+        }
+
+        private async Task SetResolution()
+        {
+            System.Collections.Generic.IReadOnlyList<IMediaEncodingProperties> res;
+            res = this.mediaCapture.VideoDeviceController.GetAvailableMediaStreamProperties(MediaStreamType.VideoPreview);
+            uint maxResolution = 0;
+            var indexMaxResolution = 0;
+
+            if (res.Count >= 1)
+            {
+                for (var i = 0; i < res.Count; i++)
+                {
+                    var vp = (VideoEncodingProperties)res[i];
+
+                    if (vp.Width > maxResolution)
+                    {
+                        indexMaxResolution = i;
+                        maxResolution = vp.Width;
+                        this.width = vp.Width;
+                        this.height = vp.Height;
+                    }
+                }
+                await this.mediaCapture.VideoDeviceController.SetMediaStreamPropertiesAsync(MediaStreamType.VideoPreview, res[indexMaxResolution]);
+            }
+        }
+
+
+        private void Page_SizeChanged(object sender, SizeChangedEventArgs e)
+        {
+            this.SetGridSize();
+        }
+
+
+        private void SetGridSize()
+        {
+            this.VideoCaptureElement.Height = this.previewGrid.Height - 100;
+            this.VideoCaptureElement.Width = this.previewGrid.Width;
+        }
+
+        #endregion
+
+
+        #region Barcode scanner
+
+        private async void cancelButton_Click(object sender, RoutedEventArgs e)
+        {
+            await this.Cleanup();
+            Cancled?.Invoke();
+        }
+
+
+        public async Task Cleanup()
+        {
+            if (!this.isReady)
+            {
+                this.isScanning = false;
+                this.timerFocus.Stop();
+                this.timerFocus.Tick -= this.timerFocus_Tick;
+
+                await this.mediaCapture.StopPreviewAsync();
+                this.mediaCapture.FocusChanged -= this.mediaCaptureManager_FocusChanged;
+
+                this.isReady = true;
+            }
+        }
+
+        private async Task StartPreview()
+        {
+            if (Windows.System.Profile.AnalyticsInfo.VersionInfo.DeviceFamily == "Windows.Mobile")
+            {
+                this.mediaCapture.SetPreviewRotation(VideoRotation.Clockwise90Degrees);
+                this.mediaCapture.SetPreviewMirroring(true);
+            }
+
+            var focusControl = this.mediaCapture.VideoDeviceController.FocusControl;
+            if (focusControl.FocusChangedSupported)
+            {
+                this.isReady = false;
+                this.isScanning = true;
+
+                this.mediaCapture.FocusChanged += this.mediaCaptureManager_FocusChanged;
+                this.VideoCaptureElement.Source = this.mediaCapture;
+                this.VideoCaptureElement.Stretch = Stretch.UniformToFill;
+                await this.mediaCapture.StartPreviewAsync();
+                await focusControl.UnlockAsync();
+                var settings = new FocusSettings { Mode = FocusMode.Continuous, AutoFocusRange = AutoFocusRange.FullRange };
+                focusControl.Configure(settings);
+                await focusControl.FocusAsync();
+            }
+            else if (focusControl.Supported)
+            {
+                this.isReady = false;
+                this.isScanning = true;
+
+                this.VideoCaptureElement.Source = this.mediaCapture;
+                this.VideoCaptureElement.Stretch = Stretch.UniformToFill;
+                await this.mediaCapture.StartPreviewAsync();
+                await focusControl.UnlockAsync();
+
+                focusControl.Configure(new FocusSettings { Mode = FocusMode.Auto });
+                this.timerFocus.Tick += this.timerFocus_Tick;
+                this.timerFocus.Interval = new TimeSpan(0, 0, 3);
+                this.timerFocus.Start();
+            }
+            else
+            {
+                await this.OnErrorAsync(new NotSupportedException("AutoFocus control is not supported on this device"));
+            }
+
+
+        }
+
+        private async void mediaCaptureManager_FocusChanged(MediaCapture sender, MediaCaptureFocusChangedEventArgs args)
+        {
+            if (this.isScanning)
+                await this.CapturePhotoFromCameraAsync();
+        }
+
+        private async void timerFocus_Tick(object sender, object e)
+        {
+            if (this.timerCaptureInProgress)
+                return; // if camera is still focusing
+
+            if (this.isScanning)
+            {
+                this.timerCaptureInProgress = true;
+
+                await this.mediaCapture.VideoDeviceController.FocusControl.FocusAsync();
+                await this.CapturePhotoFromCameraAsync();
+
+                this.timerCaptureInProgress = false;
+            }
+        }
+
+        private async Task CapturePhotoFromCameraAsync()
+        {
+            if (!this.isScanning)
+                return;
+
+            if (await this.videoCaptureSemaphore.WaitAsync(0) == true)
+            {
+                try
+                {
+                    var videoFrame = new VideoFrame(BitmapPixelFormat.Bgra8, (int)this.width, (int)this.height);
+                    await this.mediaCapture.GetPreviewFrameAsync(videoFrame);
+
+                    var bytes = await this.SaveSoftwareBitmapToBufferAsync(videoFrame.SoftwareBitmap);
+                    await this.ScanImageAsync(bytes);
+                }
+                finally
+                {
+                    this.videoCaptureSemaphore.Release();
+                }
+            }
+        }
+
+        private async Task<byte[]> SaveSoftwareBitmapToBufferAsync(SoftwareBitmap softwareBitmap)
+        {
+            byte[] bytes = null;
+
+            try
+            {
+                IRandomAccessStream stream = new InMemoryRandomAccessStream();
+
+                // Create an encoder with the desired format
+                var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.BmpEncoderId, stream);
+                encoder.SetSoftwareBitmap(softwareBitmap);
+                encoder.IsThumbnailGenerated = false;
+                await encoder.FlushAsync();
+
+                bytes = new byte[stream.Size];
+
+                // This returns IAsyncOperationWithProgess, so you can add additional progress handling
+                await stream.ReadAsync(bytes.AsBuffer(), (uint)stream.Size, Windows.Storage.Streams.InputStreamOptions.None);
+            }
+
+            catch (Exception ex)
+            {
+                Logger.LogError(ex.Message);
+            }
+
+            return bytes;
+        }
+
+
+        private async Task BarCodeFound(string barcode)
+        {
+            await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
+            {
+                this.timerFocus.Stop();
+                this.isScanning = false;
+
+                if (barcode != null)
+                {
+                    CodeFound?.Invoke(barcode);
+                }
+            });
+        }
+
+
+        private async Task OnErrorAsync(Exception e)
+        {
+            await this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => this.Error?.Invoke(e));
+
+        }
+
+        private async Task ScanImageAsync(byte[] pixelsArray)
+        {
+            await this.scanSemaphore.WaitAsync();
+            try
+            {
+                if (this.isScanning)
+                {
+
+                    var result = this._ZXingReader.Decode(pixelsArray, (int)this.width, (int)this.height, RGBLuminanceSource.BitmapFormat.Unknown);
+                    if (result != null)
+                    {
+                        await this.BarCodeFound(result.Text);
+                    }
+                }
+            }
+            catch (Exception e)
+            {
+                Logger.LogError(e.Message);
+            }
+            finally
+            {
+                this.scanSemaphore.Release();
+            }
+        }
+
+        #endregion
+
+
+        private async void UserControl_Unloaded(object sender, RoutedEventArgs e)
+        {
+            await this.Cleanup();
+        }
+
+
+        public async Task StartScan()
+        {
+            this.btnBarcodeCancel.IsEnabled = false;
+            this.mediaCapture = new MediaCapture();
+            this.isInitialized = false;
+            this.isScanning = false;
+
+            await this.InitCamera();
+
+            if (this.isInitialized == false)
+                return;
+
+
+            this._ZXingReader = new BarcodeReader()
+            {
+                AutoRotate = true,
+                Options = new ZXing.Common.DecodingOptions() { TryHarder = false, PossibleFormats = new BarcodeFormat[] { BarcodeFormat.All_1D, BarcodeFormat.QR_CODE } }
+            };
+
+            await this.StartPreview();
+            this.btnBarcodeCancel.IsEnabled = true;
+        }
+
+        private void Dispose(bool disposing)
+        {
+            if (!this.disposedValue)
+            {
+                if (disposing)
+                {
+                    this.videoCaptureSemaphore.Dispose();
+                    this.scanSemaphore.Dispose();
+                }
+
+                this.disposedValue = true;
+            }
+        }
+
+        public void Dispose()
+        {
+            // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+            this.Dispose(disposing: true);
+            GC.SuppressFinalize(this);
+        }
+    }
+}
diff --git a/Signal-Windows/Controls/QRScanner.xaml b/Signal-Windows/Controls/QRScanner.xaml
new file mode 100644
index 0000000..122d65f
--- /dev/null
+++ b/Signal-Windows/Controls/QRScanner.xaml
@@ -0,0 +1,34 @@
+<UserControl
+    x:Class="Signal_Windows.Controls.QRScanner"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    d:DesignHeight="300"
+    d:DesignWidth="400"
+    SizeChanged="Page_SizeChanged"
+    Unloaded="UserControl_Unloaded"
+    mc:Ignorable="d">
+
+    <Grid x:Name="previewGrid">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="*" />
+            <RowDefinition Height="100" />
+        </Grid.RowDefinitions>
+        <CaptureElement
+            x:Name="VideoCaptureElement"
+            Grid.Row="0"
+            HorizontalAlignment="Stretch"
+            VerticalAlignment="Stretch" />
+        <StackPanel
+            Grid.Row="1"
+            HorizontalAlignment="Center"
+            Orientation="Horizontal">
+            <Button
+                x:Name="btnBarcodeCancel"
+                Click="btnBarcodeCancel_Click"
+                Content="Cancel" />
+        </StackPanel>
+    </Grid>
+
+</UserControl>
diff --git a/Signal-Windows/Package.appxmanifest b/Signal-Windows/Package.appxmanifest
index f5a85e9..c2a0599 100644
--- a/Signal-Windows/Package.appxmanifest
+++ b/Signal-Windows/Package.appxmanifest
@@ -46,6 +46,8 @@
   <Capabilities>
     <Capability Name="internetClient" />
     <uap:Capability Name="contacts" />
+    <DeviceCapability Name="webcam"/>
+    <DeviceCapability Name="microphone"/>
   </Capabilities>
   <Extensions>
     <Extension Category="windows.certificates">
diff --git a/Signal-Windows/Signal-Windows.csproj b/Signal-Windows/Signal-Windows.csproj
index bceec9c..81d85ce 100644
--- a/Signal-Windows/Signal-Windows.csproj
+++ b/Signal-Windows/Signal-Windows.csproj
@@ -169,6 +169,9 @@
     <Compile Include="Controls\Attachment.xaml.cs">
       <DependentUpon>Attachment.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Controls\QRScanner.cs">
+      <DependentUpon>QRScanner.xaml</DependentUpon>
+    </Compile>
     <Compile Include="Controls\IdentityKeyChangeMessage.xaml.cs">
       <DependentUpon>IdentityKeyChangeMessage.xaml</DependentUpon>
     </Compile>
@@ -200,6 +203,7 @@
     <Compile Include="ViewModels\CaptchaPageViewModel.cs" />
     <Compile Include="ViewModels\ChatsAndMediaSettingsPageViewModel.cs" />
     <Compile Include="ViewModels\ConversationSettingsPageViewModel.cs" />
+    <Compile Include="ViewModels\DeviceSettingsPageViewmodel.cs" />
     <Compile Include="ViewModels\FinishRegistrationPageViewModel.cs" />
     <Compile Include="ViewModels\GlobalSettingsPageViewModel.cs" />
     <Compile Include="ViewModels\LinkPageViewModel.cs" />
@@ -231,6 +235,9 @@
     <Compile Include="Views\CaptchaPage.xaml.cs">
       <DependentUpon>CaptchaPage.xaml</DependentUpon>
     </Compile>
+    <Compile Include="Views\DeviceSettingsPage.xaml.cs">
+      <DependentUpon>DeviceSettingsPage.xaml</DependentUpon>
+    </Compile>
     <Compile Include="Views\ChatsAndMediaSettingsPage.xaml.cs">
       <DependentUpon>ChatsAndMediaSettingsPage.xaml</DependentUpon>
     </Compile>
@@ -298,6 +305,10 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
+    <Page Include="Controls\QRScanner.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
     <Page Include="Controls\IdentityKeyChangeMessage.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
@@ -354,6 +365,10 @@
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
     </Page>
+    <Page Include="Views\DeviceSettingsPage.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
     <Page Include="Views\ChatsAndMediaSettingsPage.xaml">
       <SubType>Designer</SubType>
       <Generator>MSBuild:Compile</Generator>
diff --git a/Signal-Windows/ViewModels/DeviceSettingsPageViewmodel.cs b/Signal-Windows/ViewModels/DeviceSettingsPageViewmodel.cs
new file mode 100644
index 0000000..3073867
--- /dev/null
+++ b/Signal-Windows/ViewModels/DeviceSettingsPageViewmodel.cs
@@ -0,0 +1,124 @@
+using GalaSoft.MvvmLight;
+using libsignal;
+using libsignal.util;
+using libsignalservice;
+using libsignalservice.messages.multidevice;
+using libsignalservice.util;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Signal_Windows.ViewModels
+{
+    public class DeviceSettingsPageViewmodel : ViewModelBase
+    {
+        private static readonly ILogger Logger = LibsignalLogging.CreateLogger<AdvancedSettingsPageViewModel>();
+
+        public ReadOnlyObservableCollection<DeviceViewmodel> Devices { get; }
+        private readonly ObservableCollection<DeviceViewmodel> devices;
+
+        public DeviceSettingsPageViewmodel()
+        {
+            this.devices = new ObservableCollection<DeviceViewmodel>();
+            this.Devices = new ReadOnlyObservableCollection<DeviceViewmodel>(this.devices);
+        }
+
+        public async void OnNavigatedTo()
+        {
+            await this.RefreshList();
+        }
+
+        private async Task RefreshList()
+        {
+            this.devices.Clear();
+            var devices = await App.Handle.AccountManager.GetDevicesAsync();
+            this.devices.AddRange(devices.Select(x => new DeviceViewmodel(x)));
+        }
+
+        public async Task AddDevice(Uri uri)
+        {
+            var toAdd = DeviceProtocoll.FromUri(uri);
+
+            var code = await App.Handle.AccountManager.GetNewDeviceVerificationCodeAsync();
+
+            // I'm not sure where which parameter goes...
+            // but it failes when the QR code is to old, so the first two are propably correct...
+            await App.Handle.AccountManager.AddDeviceAsync(toAdd.Uuid, toAdd.IdentetyKey.getPublicKey(),
+                     new IdentityKeyPair(Base64.Decode(App.Handle.Store.IdentityKeyPair)),
+                     Base64.Decode(App.Handle.Store.SignalingKey),
+                     code);
+
+
+            await this.RefreshList();
+        }
+
+    }
+
+    public class DeviceProtocoll
+    {
+        private DeviceProtocoll(string uuid, IdentityKey identetyKey)
+        {
+            this.Uuid = uuid;
+            this.IdentetyKey = identetyKey;
+        }
+
+        public string Uuid { get; }
+        public IdentityKey IdentetyKey { get; }
+
+        public static DeviceProtocoll FromUri(Uri uri)
+        {
+            if (uri.Scheme.ToLower() != "tsdevice")
+                throw new ArgumentException(nameof(uri), $"The protocoll must be tsdevice but was {uri.Scheme}");
+            if (uri.Query.Length <= 1)
+                throw new ArgumentException(nameof(uri), $"The querry is not valid. Quary was: {uri.Query}");
+
+            var query = uri.Query.Substring(1);
+
+            var parameters = query.Split('&');
+            string uuid = null;
+            string pub_key = null;
+
+            foreach (var item in parameters)
+            {
+                if (item.StartsWith("uuid="))
+                    uuid = item.Substring("uuid=".Length);
+                else if (item.StartsWith("pub_key="))
+                    pub_key = item.Substring("pub_key=".Length);
+            }
+
+            if (pub_key == null || uuid == null)
+            {
+                throw new ArgumentException(nameof(uri),
+                    (pub_key == null
+                    ? "The pub_key parameter is missing."
+                    : "")
+                    + (uuid == null
+                    ? "The uuid parameter is missing."
+                    : "")
+                                        );
+
+            }
+
+            var publicKeyBytes = Base64.Decode(Uri.UnescapeDataString(pub_key));
+            var identetyKey = new IdentityKey(publicKeyBytes, 0);
+            return new DeviceProtocoll(uuid, identetyKey);
+        }
+    }
+
+
+    public class DeviceViewmodel : ViewModelBase
+    {
+
+        public DeviceViewmodel(DeviceInfo device)
+        {
+            this.Name = device.Name;
+        }
+
+        public string Name { get; }
+    }
+}
diff --git a/Signal-Windows/ViewModels/ViewModelLocator.cs b/Signal-Windows/ViewModels/ViewModelLocator.cs
index a7a9fce..96445fe 100644
--- a/Signal-Windows/ViewModels/ViewModelLocator.cs
+++ b/Signal-Windows/ViewModels/ViewModelLocator.cs
@@ -46,6 +46,7 @@ public ViewModelLocator()
             SimpleIoc.Default.Register<AppearanceSettingsPageViewModel>();
             SimpleIoc.Default.Register<ChatsAndMediaSettingsPageViewModel>();
             SimpleIoc.Default.Register<AdvancedSettingsPageViewModel>();
+            SimpleIoc.Default.Register<DeviceSettingsPageViewmodel>();
             SimpleIoc.Default.Register<BlockedContactsPageViewModel>();
             SimpleIoc.Default.Register<CaptchaPageViewModel>();
         }
@@ -144,6 +145,11 @@ public AdvancedSettingsPageViewModel AdvancedSettingsPageInstance
             get { return ServiceLocator.Current.GetInstance<AdvancedSettingsPageViewModel>(Key.ToString()); }
         }
 
+        public DeviceSettingsPageViewmodel DeviceSettingsPageInstance
+        {
+            get { return ServiceLocator.Current.GetInstance<DeviceSettingsPageViewmodel>(Key.ToString()); }
+        }
+
         public BlockedContactsPageViewModel BlockedContactsPageInstance
         {
             get { return ServiceLocator.Current.GetInstance<BlockedContactsPageViewModel>(Key.ToString()); }
diff --git a/Signal-Windows/Views/DeviceSettingsPage.xaml b/Signal-Windows/Views/DeviceSettingsPage.xaml
new file mode 100644
index 0000000..813fadb
--- /dev/null
+++ b/Signal-Windows/Views/DeviceSettingsPage.xaml
@@ -0,0 +1,40 @@
+<Page
+    x:Class="Signal_Windows.Views.DeviceSettingsPage"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:controls="using:Signal_Windows.Controls"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:local="using:Signal_Windows.Views"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
+    DataContext="{Binding DeviceSettingsPageInstance, Source={StaticResource Locator}}"
+    mc:Ignorable="d">
+
+    <Grid x:Name="root" SizeChanged="root_SizeChanged">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto" />
+            <RowDefinition />
+        </Grid.RowDefinitions>
+        <Popup
+            x:Name="ScannerPopup"
+            Grid.RowSpan="2"
+            IsLightDismissEnabled="False"
+            IsOpen="False">
+            <Grid>
+                <controls:QRScanner x:Name="scanner" CodeFound="CodeFound" Cancled="scanner_Cancled" />
+            </Grid>
+        </Popup>
+
+
+        <Button
+            x:Name="startScanButton"
+            Grid.Row="0"
+            Click="startScanButton_Click"
+            Content="Add Device"
+            FontSize="22" />
+        <ListView
+            Grid.Row="1"
+            DisplayMemberPath="Name"
+            ItemsSource="{Binding Devices}" />
+    </Grid>
+</Page>
diff --git a/Signal-Windows/Views/DeviceSettingsPage.xaml.cs b/Signal-Windows/Views/DeviceSettingsPage.xaml.cs
new file mode 100644
index 0000000..c0c327d
--- /dev/null
+++ b/Signal-Windows/Views/DeviceSettingsPage.xaml.cs
@@ -0,0 +1,96 @@
+using Signal_Windows.ViewModels;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.UI.Core;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+
+// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
+
+namespace Signal_Windows.Views
+{
+    /// <summary>
+    /// An empty page that can be used on its own or navigated to within a Frame.
+    /// </summary>
+    public sealed partial class DeviceSettingsPage : Page
+    {
+        public DeviceSettingsPage()
+        {
+            this.InitializeComponent();
+        }
+
+        public DeviceSettingsPageViewmodel Vm
+        {
+            get { return (DeviceSettingsPageViewmodel)this.DataContext; }
+        }
+
+
+        protected override void OnNavigatedTo(NavigationEventArgs e)
+        {
+            Utils.EnableBackButton(this.BackButton_Click);
+            this.Vm.OnNavigatedTo();
+            Application.Current.Suspending += this.App_Suspending;
+        }
+
+        protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
+        {
+            Utils.DisableBackButton(this.BackButton_Click);
+            Application.Current.Suspending -= this.App_Suspending;
+        }
+
+        private async void App_Suspending(object sender, Windows.ApplicationModel.SuspendingEventArgs e)
+        {
+            var deferral = e.SuspendingOperation.GetDeferral();
+            await this.scanner.Cleanup();
+            this.ScannerPopup.IsOpen = false;
+            deferral.Complete();
+        }
+
+
+        private void BackButton_Click(object sender, BackRequestedEventArgs e)
+        {
+            this.Frame.GoBack();
+            e.Handled = true;
+        }
+
+
+        private async void CodeFound(string barcode)
+        {
+            this.ScannerPopup.IsOpen = false;
+            await this.Vm.AddDevice(new Uri(barcode));
+        }
+
+        private void OnError(Exception e)
+        {
+
+        }
+
+        private async void startScanButton_Click(object sender, RoutedEventArgs e)
+        {
+            ScannerPopup.IsOpen = true;
+            await this.scanner.StartScan();
+        }
+
+        private void root_SizeChanged(object sender, SizeChangedEventArgs e)
+        {
+            this.scanner.Width = this.root.ActualWidth;
+            this.scanner.Height = this.root.ActualHeight;
+        }
+
+        private void scanner_Cancled()
+        {
+            this.ScannerPopup.IsOpen = false;
+
+        }
+    }
+}
diff --git a/Signal-Windows/Views/GlobalSettingsPage.xaml b/Signal-Windows/Views/GlobalSettingsPage.xaml
index e7796a5..8b3128d 100644
--- a/Signal-Windows/Views/GlobalSettingsPage.xaml
+++ b/Signal-Windows/Views/GlobalSettingsPage.xaml
@@ -54,6 +54,12 @@
                     <TextBlock Text="Chats &amp; Media" Style="{StaticResource TitleTextBlockStyle}" Margin="8,0,0,0"/>
                 </StackPanel>
             </Button>
+            <Button x:Name="LinkedDevices" Background="{x:Null}" HorizontalContentAlignment="Left" Width="250" Click="LinkedDevices_Click">
+                <StackPanel Orientation="Horizontal">
+                    <FontIcon Glyph="&#xE8EA;" FontSize="36"/>
+                    <TextBlock Text="Linked Devices" Style="{StaticResource TitleTextBlockStyle}" Margin="8,0,0,0"/>
+                </StackPanel>
+            </Button>
             <Button x:Name="AdvancedButton" Background="{x:Null}" HorizontalContentAlignment="Left" Width="250" Click="AdvancedButton_Click">
                 <StackPanel Orientation="Horizontal">
                     <FontIcon Glyph="&#xE15E;" FontSize="36"/>
diff --git a/Signal-Windows/Views/GlobalSettingsPage.xaml.cs b/Signal-Windows/Views/GlobalSettingsPage.xaml.cs
index 44f3d08..60674b8 100644
--- a/Signal-Windows/Views/GlobalSettingsPage.xaml.cs
+++ b/Signal-Windows/Views/GlobalSettingsPage.xaml.cs
@@ -79,5 +79,10 @@ private void AboutButton_Click(object sender, RoutedEventArgs e)
         {
             Frame.Navigate(typeof(AboutPage));
         }
+
+        private void LinkedDevices_Click(object sender, RoutedEventArgs e)
+        {
+            Frame.Navigate(typeof(DeviceSettingsPage));
+        }
     }
 }

From 8d05df6a495cbad86367a10f56d9266f3a8df437 Mon Sep 17 00:00:00 2001
From: Patrick Kranz <patrick-kranz@live.de>
Date: Sun, 16 May 2021 14:37:13 +0200
Subject: [PATCH 2/2] Working on device linking

---
 Signal-Windows/Controls/QRScanner.cs          | 16 +++++--
 Signal-Windows/Controls/QRScanner.xaml        |  4 +-
 .../ViewModels/DeviceSettingsPageViewmodel.cs | 43 ++++++++++++-----
 .../Views/AdvancedSettingsPage.xaml           | 46 +++++++++----------
 4 files changed, 69 insertions(+), 40 deletions(-)

diff --git a/Signal-Windows/Controls/QRScanner.cs b/Signal-Windows/Controls/QRScanner.cs
index 4d362c1..a7c531b 100644
--- a/Signal-Windows/Controls/QRScanner.cs
+++ b/Signal-Windows/Controls/QRScanner.cs
@@ -154,8 +154,17 @@ private async Task StartPreview()
             }
 
             var focusControl = this.mediaCapture.VideoDeviceController.FocusControl;
-            if (focusControl.FocusChangedSupported)
+            if (focusControl.Supported && focusControl.FocusChangedSupported)
             {
+
+                var state = focusControl.FocusState;
+                var mode = focusControl.Mode;
+                var presset = focusControl.Preset;
+                var modes = focusControl.SupportedFocusModes.ToArray();
+                var presets = focusControl.SupportedPresets.ToArray();
+
+
+
                 this.isReady = false;
                 this.isScanning = true;
 
@@ -185,6 +194,7 @@ private async Task StartPreview()
             }
             else
             {
+                var capabilits = this.mediaCapture.VideoDeviceController.Focus.Capabilities;
                 await this.OnErrorAsync(new NotSupportedException("AutoFocus control is not supported on this device"));
             }
 
@@ -321,7 +331,7 @@ private async void UserControl_Unloaded(object sender, RoutedEventArgs e)
 
         public async Task StartScan()
         {
-            this.btnBarcodeCancel.IsEnabled = false;
+            this.cancleButton.IsEnabled = false;
             this.mediaCapture = new MediaCapture();
             this.isInitialized = false;
             this.isScanning = false;
@@ -339,7 +349,7 @@ public async Task StartScan()
             };
 
             await this.StartPreview();
-            this.btnBarcodeCancel.IsEnabled = true;
+            this.cancleButton.IsEnabled = true;
         }
 
         private void Dispose(bool disposing)
diff --git a/Signal-Windows/Controls/QRScanner.xaml b/Signal-Windows/Controls/QRScanner.xaml
index 122d65f..eed2b54 100644
--- a/Signal-Windows/Controls/QRScanner.xaml
+++ b/Signal-Windows/Controls/QRScanner.xaml
@@ -25,8 +25,8 @@
             HorizontalAlignment="Center"
             Orientation="Horizontal">
             <Button
-                x:Name="btnBarcodeCancel"
-                Click="btnBarcodeCancel_Click"
+                x:Name="cancleButton"
+                Click="cancelButton_Click"
                 Content="Cancel" />
         </StackPanel>
     </Grid>
diff --git a/Signal-Windows/ViewModels/DeviceSettingsPageViewmodel.cs b/Signal-Windows/ViewModels/DeviceSettingsPageViewmodel.cs
index 3073867..967c39b 100644
--- a/Signal-Windows/ViewModels/DeviceSettingsPageViewmodel.cs
+++ b/Signal-Windows/ViewModels/DeviceSettingsPageViewmodel.cs
@@ -1,10 +1,12 @@
 using GalaSoft.MvvmLight;
 using libsignal;
+using libsignal.ecc;
 using libsignal.util;
 using libsignalservice;
 using libsignalservice.messages.multidevice;
 using libsignalservice.util;
 using Microsoft.Extensions.Logging;
+using Org.BouncyCastle.Crypto.Parameters;
 using System;
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
@@ -46,13 +48,25 @@ public async Task AddDevice(Uri uri)
 
             var code = await App.Handle.AccountManager.GetNewDeviceVerificationCodeAsync();
 
-            // I'm not sure where which parameter goes...
-            // but it failes when the QR code is to old, so the first two are propably correct...
-            await App.Handle.AccountManager.AddDeviceAsync(toAdd.Uuid, toAdd.IdentetyKey.getPublicKey(),
-                     new IdentityKeyPair(Base64.Decode(App.Handle.Store.IdentityKeyPair)),
-                     Base64.Decode(App.Handle.Store.SignalingKey),
-                     code);
 
+            // is the SignalingKey the profileKey???
+            var profileKey = Base64.Decode(App.Handle.Store.SignalingKey);
+            var identityKeyPair = new IdentityKeyPair(Base64.Decode(App.Handle.Store.IdentityKeyPair));
+
+            try
+            {
+                // I'm not sure where which parameter goes...
+                // but it failes when the QR code is to old, so the first two are propably correct...
+                await App.Handle.AccountManager.AddDeviceAsync(toAdd.Uuid, toAdd.PublicKey,
+                         identityKeyPair,
+                        profileKey,
+                         code);
+            }
+            catch (libsignalservice.push.exceptions.NotFoundException)
+            {
+
+                // ToDo: Handle Divice not found
+            }
 
             await this.RefreshList();
         }
@@ -61,14 +75,14 @@ await App.Handle.AccountManager.AddDeviceAsync(toAdd.Uuid, toAdd.IdentetyKey.get
 
     public class DeviceProtocoll
     {
-        private DeviceProtocoll(string uuid, IdentityKey identetyKey)
+        private DeviceProtocoll(string uuid, ECPublicKey identetyKey)
         {
             this.Uuid = uuid;
-            this.IdentetyKey = identetyKey;
+            this.PublicKey = identetyKey;
         }
 
         public string Uuid { get; }
-        public IdentityKey IdentetyKey { get; }
+        public ECPublicKey PublicKey { get; }
 
         public static DeviceProtocoll FromUri(Uri uri)
         {
@@ -104,9 +118,14 @@ public static DeviceProtocoll FromUri(Uri uri)
 
             }
 
-            var publicKeyBytes = Base64.Decode(Uri.UnescapeDataString(pub_key));
-            var identetyKey = new IdentityKey(publicKeyBytes, 0);
-            return new DeviceProtocoll(uuid, identetyKey);
+            uuid = Uri.UnescapeDataString(uuid);
+
+            var publicKeyBytes = Base64.DecodeWithoutPadding(Uri.UnescapeDataString(pub_key));
+
+
+            var publicKey = Curve.decodePoint(publicKeyBytes, 0);
+
+            return new DeviceProtocoll(uuid, publicKey);
         }
     }
 
diff --git a/Signal-Windows/Views/AdvancedSettingsPage.xaml b/Signal-Windows/Views/AdvancedSettingsPage.xaml
index 7047690..7f3f8d0 100644
--- a/Signal-Windows/Views/AdvancedSettingsPage.xaml
+++ b/Signal-Windows/Views/AdvancedSettingsPage.xaml
@@ -1,24 +1,24 @@
-<Page
-    x:Class="Signal_Windows.Views.AdvancedSettingsPage"
-    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    xmlns:local="using:Signal_Windows.Views"
-    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-    mc:Ignorable="d"
-    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
-    DataContext="{Binding AdvancedSettingsPageInstance, Source={StaticResource Locator}}">
-
-    <Grid>
-        <Grid.RowDefinitions>
-            <RowDefinition Height="1*"/>
-            <RowDefinition Height="8*"/>
-        </Grid.RowDefinitions>
-        <TextBlock Text="Advanced" HorizontalAlignment="Center" VerticalAlignment="Center" Style="{StaticResource TitleTextBlockStyle}"/>
-        <StackPanel Grid.Row="1" Margin="32,0,0,0">
-            <Button x:Name="ExportDebugLogButton" Content="Export debug log" Click="ExportDebugLogButton_Click"/>
+<Page
+    x:Class="Signal_Windows.Views.AdvancedSettingsPage"
+    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:local="using:Signal_Windows.Views"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    mc:Ignorable="d"
+    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
+    DataContext="{Binding AdvancedSettingsPageInstance, Source={StaticResource Locator}}">
+
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="1*"/>
+            <RowDefinition Height="8*"/>
+        </Grid.RowDefinitions>
+        <TextBlock Text="Advanced" HorizontalAlignment="Center" VerticalAlignment="Center" Style="{StaticResource TitleTextBlockStyle}"/>
+        <StackPanel Grid.Row="1" Margin="32,0,0,0">
+            <Button x:Name="ExportDebugLogButton" Content="Export debug log" Click="ExportDebugLogButton_Click"/>
             <Button x:Name="SyncButton" Content="Request contact and group sync" Margin="0,8,0,0" Click="SyncButton_Click"/>
-            <Button x:Name="TestCrashButton" Content="Intentionally crash the app to test its debug log export" Click="TestCrashButton_Click"/>
-        </StackPanel>
-    </Grid>
-</Page>
+            <Button x:Name="TestCrashButton" Content="Intentionally crash the app to test its debug log export" Click="TestCrashButton_Click"/>
+        </StackPanel>
+    </Grid>
+</Page>