Skip to content

Commit

Permalink
Implement QR code scanning from screen
Browse files Browse the repository at this point in the history
Closes #20
  • Loading branch information
timokoessler committed May 9, 2024
1 parent fcc39ff commit ac1b392
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 5 deletions.
3 changes: 2 additions & 1 deletion Guard/Core/Import/Importer/IImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ internal interface IImporter
internal enum ImportType
{
File,
Clipboard
Clipboard,
ScreenCapture
}

internal abstract string Name { get; }
Expand Down
67 changes: 67 additions & 0 deletions Guard/Core/Import/Importer/QRScreenCaptureImporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.Drawing;
using System.Windows;
using Guard.Core.Models;

namespace Guard.Core.Import.Importer
{
internal class QRScreenCaptureImporter : IImporter
{
public string Name => "QRScreenCapture";
public IImporter.ImportType Type => IImporter.ImportType.ScreenCapture;
public string SupportedFileExtensions => "";

public bool RequiresPassword(string? path) => false;

public (int total, int duplicate, int tokenID) Parse(string? path, byte[]? password)
{
MainWindow mainWindow = (MainWindow)Application.Current.MainWindow;

mainWindow.MinimizeWindow();

Bitmap capture = ScreenCapture.CaptureAllScreens();

mainWindow.RestoreWindow();

string? text =
QRCode.ParseQRBitmap(capture)
?? throw new Exception(I18n.GetString("import.noqrfound.screen"));

if (!text.StartsWith("otpauth"))
{
throw new Exception(I18n.GetString("import.invalidqr.screen"));
}

// Google Authenticator
if (text.StartsWith("otpauth-migration:"))
{
int duplicateTokens = 0;
List<OTPUri> otpUris = GoogleAuthenticator.Parse(text);
if (otpUris.Count == 0)
{
throw new Exception(
"Google Authenticator migration failed because no tokens were found."
);
}
foreach (OTPUri gOTPUri in otpUris)
{
DBTOTPToken gDBToken = OTPUriParser.ConvertToDBToken(gOTPUri);
if (!TokenManager.AddToken(gDBToken))
{
duplicateTokens++;
}
}
return (otpUris.Count, duplicateTokens, 0);
}

OTPUri otpUri = OTPUriParser.Parse(text);
DBTOTPToken dbToken = OTPUriParser.ConvertToDBToken(otpUri);

if (!TokenManager.AddToken(dbToken))
{
return (1, 1, 0);
}

return (1, 0, dbToken.Id);
}
}
}
35 changes: 35 additions & 0 deletions Guard/Core/Import/ScreenCapture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Drawing;
using System.Runtime.InteropServices;

namespace Guard.Core.Import
{
internal class ScreenCapture
{
private enum SystemMetric
{
SM_XVIRTUALSCREEN = 76, // 0x4C
SM_YVIRTUALSCREEN = 77, // 0x4D
SM_CXVIRTUALSCREEN = 78, // 0x4E
SM_CYVIRTUALSCREEN = 79, // 0x4F
}

[DllImport("user32.dll")]
static extern int GetSystemMetrics(SystemMetric smIndex);

public static Bitmap CaptureAllScreens()
{
int screenLeft = GetSystemMetrics(SystemMetric.SM_XVIRTUALSCREEN);
int screenTop = GetSystemMetrics(SystemMetric.SM_YVIRTUALSCREEN);
int screenWidth = GetSystemMetrics(SystemMetric.SM_CXVIRTUALSCREEN);
int screenHeight = GetSystemMetrics(SystemMetric.SM_CYVIRTUALSCREEN);

Bitmap bmp = new(screenWidth, screenHeight);
using (Graphics g = Graphics.FromImage(bmp))
{
g.CopyFromScreen(screenLeft, screenTop, 0, 0, bmp.Size);
}

return bmp;
}
}
}
13 changes: 13 additions & 0 deletions Guard/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -389,5 +389,18 @@ protected override void OnClosing(CancelEventArgs e)
}
base.OnClosing(e);
}

internal void MinimizeWindow()
{
WindowStyle tempStyle = WindowStyle;
WindowStyle = WindowStyle.None;
WindowState = WindowState.Minimized;
WindowStyle = tempStyle;
}

internal void RestoreWindow()
{
WindowState = WindowState.Normal;
}
}
}
4 changes: 3 additions & 1 deletion Guard/Resources/Strings.de.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
<system:String x:Key="i.add.qr">QR-Code importieren</system:String>
<system:String x:Key="i.add.qr.description">Importiere einen QR-Code aus einer Bilddatei</system:String>
<system:String x:Key="i.add.qrscreen">QR-Code vom Bildschirm scannen</system:String>
<system:String x:Key="i.add.qrscreen.description">Wähle einen Bildschirmbereich aus, um den QR-Code zu scannen</system:String>
<system:String x:Key="i.add.qrscreen.description">Erkennt einen sichtbaren QR-Code auf dem Bildschirm</system:String>
<system:String x:Key="i.add.clipboard">Aus Zwischenablage hinzufügen</system:String>
<system:String x:Key="i.add.clipboard.description">Bild eines QR-Codes oder TOTP URI einfügen</system:String>
<system:String x:Key="i.add.import">Importieren</system:String>
Expand All @@ -146,6 +146,8 @@
<system:String x:Key="i.import.failed.title">Import fehlgeschlagen</system:String>
<system:String x:Key="i.import.failed.content">Beim Import ist ein Fehler aufgetreten:</system:String>
<system:String x:Key="i.import.noqrfound">Es wurde kein QR-Code erkannt</system:String>
<system:String x:Key="i.import.noqrfound.screen">Es wurde kein QR-Code erkannt. Bitte stelle sicher, dass kein Fenster den QR-Code verdeckt und dass der QR-Code gut sichtbar ist.</system:String>
<system:String x:Key="i.import.invalidqr.screen">Der QR-Code enthält keine gültigen Informationen über einen Token. Bitte stelle auch sicher, dass nur ein QR-Code auf dem Bildschirm sichtbar ist.</system:String>
<system:String x:Key="i.import.clipboard.invalid">Die Zwischenablage enthält kein Bild und keinen gültigen Text</system:String>
<system:String x:Key="i.import.clipboard.invalid.multifiles">Es wird aktuell nur eine Datei gleichzeitig unterstützt.</system:String>
<system:String x:Key="i.import.gauthenticator">Google Authenticator</system:String>
Expand Down
4 changes: 3 additions & 1 deletion Guard/Resources/Strings.en.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
<system:String x:Key="i.add.qr">Import QR code</system:String>
<system:String x:Key="i.add.qr.description">Scan a QR code by importing an image file</system:String>
<system:String x:Key="i.add.qrscreen">Scan QR code from the screen</system:String>
<system:String x:Key="i.add.qrscreen.description">Select a screen area to scan the QR code</system:String>
<system:String x:Key="i.add.qrscreen.description">Detects a visible QR code on the screen</system:String>
<system:String x:Key="i.add.clipboard">Add from clipboard</system:String>
<system:String x:Key="i.add.clipboard.description">Paste a QR code image or a TOTP URI</system:String>
<system:String x:Key="i.add.import">Import</system:String>
Expand All @@ -140,6 +140,8 @@
<system:String x:Key="i.import.failed.title">Import failed</system:String>
<system:String x:Key="i.import.failed.content">An error occurred while importing:</system:String>
<system:String x:Key="i.import.noqrfound">Could not detect a QR code</system:String>
<system:String x:Key="i.import.noqrfound.screen">No QR code was detected. Please make sure that no window is covering the QR code and that the QR code is clearly visible.</system:String>
<system:String x:Key="i.import.invalidqr.screen">The QR code does not contain valid information about a token. Please also make sure that only one QR code is visible on the screen.</system:String>
<system:String x:Key="i.import.clipboard.invalid">The clipboard does not contain an image or text.</system:String>
<system:String x:Key="i.import.clipboard.invalid.multifiles">Currently only one file can be imported from the clipboard at a time.</system:String>
<system:String x:Key="i.import.duplicate">A token with the same secret key already exists</system:String>
Expand Down
4 changes: 3 additions & 1 deletion Guard/Resources/Strings.zh_cn.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
<system:String x:Key="i.add.qr">导入二维码</system:String>
<system:String x:Key="i.add.qr.description">通过导入图像文件扫描二维码</system:String>
<system:String x:Key="i.add.qrscreen">从屏幕上扫描二维码</system:String>
<system:String x:Key="i.add.qrscreen.description">选择一个屏幕区域扫描二维码</system:String>
<system:String x:Key="i.add.qrscreen.description">Detects a visible QR code on the screen</system:String>
<system:String x:Key="i.add.clipboard">从剪贴板添加</system:String>
<system:String x:Key="i.add.clipboard.description">粘贴二维码图像或TOTP URI</system:String>
<system:String x:Key="i.add.import">导入</system:String>
Expand All @@ -140,6 +140,8 @@
<system:String x:Key="i.import.failed.title">导入失败</system:String>
<system:String x:Key="i.import.failed.content">导入时发生错误:</system:String>
<system:String x:Key="i.import.noqrfound">未能检测到二维码</system:String>
<system:String x:Key="i.import.noqrfound.screen">No QR code was detected. Please make sure that no window is covering the QR code and that the QR code is clearly visible.</system:String>
<system:String x:Key="i.import.invalidqr.screen">The QR code does not contain valid information about a token. Please also make sure that only one QR code is visible on the screen.</system:String>
<system:String x:Key="i.import.clipboard.invalid">剪贴板不包含图像或文本。</system:String>
<system:String x:Key="i.import.clipboard.invalid.multifiles">目前一次只能从剪贴板导入一个文件。</system:String>
<system:String x:Key="i.import.duplicate">已存在具有相同密钥的令牌</system:String>
Expand Down
4 changes: 3 additions & 1 deletion Guard/Resources/Strings.zh_tw.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
<system:String x:Key="i.add.qr">匯入QR碼</system:String>
<system:String x:Key="i.add.qr.description">通過匯入圖像檔案掃描QR碼</system:String>
<system:String x:Key="i.add.qrscreen">從螢幕上掃描QR碼</system:String>
<system:String x:Key="i.add.qrscreen.description">選擇一個螢幕區域掃描QR碼</system:String>
<system:String x:Key="i.add.qrscreen.description">Detects a visible QR code on the screen</system:String>
<system:String x:Key="i.add.clipboard">從剪貼簿新增</system:String>
<system:String x:Key="i.add.clipboard.description">貼上QR碼圖像或TOTP URI</system:String>
<system:String x:Key="i.add.import">匯入</system:String>
Expand All @@ -140,6 +140,8 @@
<system:String x:Key="i.import.failed.title">匯入失敗</system:String>
<system:String x:Key="i.import.failed.content">匯入時發生錯誤:</system:String>
<system:String x:Key="i.import.noqrfound">未能檢測到QR碼</system:String>
<system:String x:Key="i.import.noqrfound.screen">No QR code was detected. Please make sure that no window is covering the QR code and that the QR code is clearly visible.</system:String>
<system:String x:Key="i.import.invalidqr.screen">The QR code does not contain valid information about a token. Please also make sure that only one QR code is visible on the screen.</system:String>
<system:String x:Key="i.import.clipboard.invalid">剪貼簿不包含圖像或文字。</system:String>
<system:String x:Key="i.import.clipboard.invalid.multifiles">目前一次只能從剪貼簿匯入一個檔案。</system:String>
<system:String x:Key="i.import.duplicate">已存在具有相同金鑰的令牌</system:String>
Expand Down
20 changes: 20 additions & 0 deletions Guard/Views/Pages/Add/AddOverview.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,26 @@
TextWrapping="WrapWithOverflow" />
</StackPanel>
</ui:CardAction>
<ui:CardAction
Width="350"
Height="95"
Margin="0,15,15,0"
VerticalAlignment="Top"
Click="QR_ScreenCapture_Click">
<ui:CardAction.Icon>
<ui:SymbolIcon FontSize="32px" Symbol="Desktop24" />
</ui:CardAction.Icon>
<StackPanel Margin="0,0,8,0">
<ui:TextBlock
FontTypography="BodyStrong"
Text="{DynamicResource i.add.qrscreen}"
TextWrapping="WrapWithOverflow" />
<ui:TextBlock
Appearance="Secondary"
Text="{DynamicResource i.add.qrscreen.description}"
TextWrapping="WrapWithOverflow" />
</StackPanel>
</ui:CardAction>
<ui:CardAction
Width="350"
Height="95"
Expand Down
15 changes: 15 additions & 0 deletions Guard/Views/Pages/Add/AddOverview.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ private async void Import(IImporter importer)
}
(total, duplicate, tokenID) = importer.Parse(null, null);
}
else if (importer.Type == IImporter.ImportType.ScreenCapture)
{
if (importer.RequiresPassword(""))
{
throw new NotImplementedException(
"Importers that require a password are not supported for screen capture"
);
}
(total, duplicate, tokenID) = importer.Parse(null, null);
}
else
{
throw new Exception("Invalid Importer Type");
Expand Down Expand Up @@ -181,5 +191,10 @@ private void Aegis_Click(object sender, RoutedEventArgs e)
{
Import(new AegisAuthenticatorImporter());
}

private void QR_ScreenCapture_Click(object sender, RoutedEventArgs e)
{
Import(new QRScreenCaptureImporter());
}
}
}

0 comments on commit ac1b392

Please sign in to comment.