mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-03 17:56:44 +02:00
[New PowerToy] OCR PowerToy (#19172)
* Init commit * Fix unintended GUID change of Microsoft.PowerToys.Run.Plugin.TimeZone.UnitTests * Region and click word working * Code style * Close even when there is no result from the OCR * Fix spelling concerns, and make overlay black to match snipping tool * increase opacity of overlay to match snipping tool * Code Style and cleanup * Code style * Create Logos and hook them into the project file * Make the PowerOCR VCXProj more like Awake VCXProj * Rename MainWindow to OCROverlay * Add WindowUtilities and WindowForms * Remove fsg to fix spelling error * launch OCR Overlay on every screen * Add PowerOCR to Runner Main.cpp * Add PowerOCR Settings and Properties * Add PowerOcrViewModel * Fix wrong setting reference in PowerOcrSettingsVM * Try to clean up the Cpp project for PowerOCR * Went to ARM64 was x64 thanks @snickler * Clean up PowerOCR C++ Proj with file refs * Rewrite C++ dllmain comparing to awake * Changes for spelling issues. The rest will stay * Create PowerOcr Settings Page and add to settings shell * Fix PowerOcr Settings * Fix multi-monitor scaling issue * Add close all overlays when escaping * Update src/runner/main.cpp to call correct Power OCR dll Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com> * Update expect.txt * Add many files from Color Picker for hotkey activation * Organize project into helper folder * Use new hotkey activation and keep process alive * Fix bug where scalebmp wasn't working * Add The file headers and dispose app.xaml.cs * Code style changes * Fix bug where PowerOCR was toggling Awake * Unsubscribe from keyboard events making they don't fire twice * Add SndPowerOcrSetting and add to SettingsVM * Trying to make the runner close PowerOCR when runner closes * Fix app_name * Update spellcheck expect * use mutex on PowerOCR app to keep to single instance * Rebuild the module interface using ColorPicker as a template. Process still stays alive. * Fix project names of the module interface * Put app startup args back to 0 like color picker * Runner now finds and enables/disables PowerOCR * remove unneeded item groups from settings proj, per stefansjfw * Add PowerOCR Screenshots * Revert changed project GUID * Add OOBE content for PowerOCR * Keep cursor on one screen since the OCR window does not span screens. * reload settings when activation key is pressed * New screenshots and OOBE text * Add PowerOCR as a case in the settings App.xaml.cs OnLaunched * Settings and OOBE Text Changes * Using using on bitmaps and change OCR overlay to stay open if no result * Keyboard activation is handled is true * Remove unused start PowerOCR OOBE Method * [PowerOCR]Add some telemetry * Add some logging * Don't recreate the OCR overlay Windows more times * Add to BugReportTool to get event viewer errors * Fix wrong comment * Fix another comment * Add files to installer * Add to signing * Don't take Esc away from other apps * Default to Win Shift R * Use low level keyboard hook from runner * Remove esc from local low level keyboard hook * Fix some nits * Default to Win Shift T
This commit is contained in:
10
src/modules/PowerOCR/PowerOCR/App.xaml
Normal file
10
src/modules/PowerOCR/PowerOCR/App.xaml
Normal file
@@ -0,0 +1,10 @@
|
||||
<Application
|
||||
x:Class="PowerOCR.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:PowerOCR"
|
||||
ShutdownMode="OnExplicitShutdown"
|
||||
Exit="Application_Exit"
|
||||
Startup="Application_Startup">
|
||||
<Application.Resources />
|
||||
</Application>
|
||||
88
src/modules/PowerOCR/PowerOCR/App.xaml.cs
Normal file
88
src/modules/PowerOCR/PowerOCR/App.xaml.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using ManagedCommon;
|
||||
using PowerOCR.Helpers;
|
||||
using PowerOCR.Keyboard;
|
||||
using PowerOCR.Settings;
|
||||
|
||||
namespace PowerOCR;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for App.xaml
|
||||
/// </summary>
|
||||
public partial class App : Application, IDisposable
|
||||
{
|
||||
private KeyboardMonitor? keyboardMonitor;
|
||||
private EventMonitor? eventMonitor;
|
||||
private Mutex? _instanceMutex;
|
||||
private int _powerToysRunnerPid;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
keyboardMonitor?.Dispose();
|
||||
}
|
||||
|
||||
private void Application_Startup(object sender, StartupEventArgs e)
|
||||
{
|
||||
// allow only one instance of PowerOCR
|
||||
_instanceMutex = new Mutex(true, @"Local\PowerToys_PowerOCR_InstanceMutex", out bool createdNew);
|
||||
if (!createdNew)
|
||||
{
|
||||
Logger.LogWarning("Another running PowerOCR instance was detected. Exiting PowerOCR");
|
||||
_instanceMutex = null;
|
||||
Environment.Exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Args?.Length > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = int.TryParse(e.Args[0], out _powerToysRunnerPid);
|
||||
Logger.LogInfo($"PowerOCR started from the PowerToys Runner. Runner pid={_powerToysRunnerPid}");
|
||||
|
||||
RunnerHelper.WaitForPowerToysRunner(_powerToysRunnerPid, () =>
|
||||
{
|
||||
Logger.LogInfo("PowerToys Runner exited. Exiting PowerOCR");
|
||||
Environment.Exit(0);
|
||||
});
|
||||
var userSettings = new UserSettings(new Helpers.ThrottledActionInvoker());
|
||||
eventMonitor = new EventMonitor();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.LogInfo($"PowerOCR started detached from PowerToys Runner.");
|
||||
_powerToysRunnerPid = -1;
|
||||
var userSettings = new UserSettings(new Helpers.ThrottledActionInvoker());
|
||||
keyboardMonitor = new KeyboardMonitor(userSettings);
|
||||
keyboardMonitor?.Start();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnExit(ExitEventArgs e)
|
||||
{
|
||||
if (_instanceMutex != null)
|
||||
{
|
||||
_instanceMutex.ReleaseMutex();
|
||||
}
|
||||
|
||||
base.OnExit(e);
|
||||
}
|
||||
|
||||
private void Application_Exit(object sender, ExitEventArgs e)
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
}
|
||||
57
src/modules/PowerOCR/PowerOCR/Helpers/CursorClipper.cs
Normal file
57
src/modules/PowerOCR/PowerOCR/Helpers/CursorClipper.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace PowerOCR.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Functions to constrain the mouse cursor (typically used when dragging)
|
||||
/// </summary>
|
||||
public static class CursorClipper
|
||||
{
|
||||
/// <summary>
|
||||
/// Constrain mouse cursor to the area of the specified UI element.
|
||||
/// </summary>
|
||||
/// <param name="element">Target UI element.</param>
|
||||
/// <returns>True on success.</returns>
|
||||
public static bool ClipCursor(FrameworkElement element)
|
||||
{
|
||||
const double dpi96 = 96.0;
|
||||
|
||||
var topLeft = element.PointToScreen(new Point(0, 0));
|
||||
|
||||
PresentationSource source = PresentationSource.FromVisual(element);
|
||||
if (source?.CompositionTarget == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
double dpiX = dpi96 * source.CompositionTarget.TransformToDevice.M11;
|
||||
double dpiY = dpi96 * source.CompositionTarget.TransformToDevice.M22;
|
||||
|
||||
var width = (int)((element.ActualWidth + 1) * dpiX / dpi96);
|
||||
var height = (int)((element.ActualHeight + 1) * dpiY / dpi96);
|
||||
|
||||
OSInterop.RECT rect = new OSInterop.RECT
|
||||
{
|
||||
Left = (int)topLeft.X,
|
||||
Top = (int)topLeft.Y,
|
||||
Right = (int)topLeft.X + width,
|
||||
Bottom = (int)topLeft.Y + height,
|
||||
};
|
||||
|
||||
return OSInterop.ClipCursor(ref rect);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove any mouse cursor constraint.
|
||||
/// </summary>
|
||||
/// <returns>True on success.</returns>
|
||||
public static bool UnClipCursor()
|
||||
{
|
||||
return OSInterop.ClipCursor(IntPtr.Zero);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace PowerOCR.Helpers;
|
||||
|
||||
public interface IThrottledActionInvoker
|
||||
{
|
||||
void ScheduleAction(Action action, int milliseconds);
|
||||
}
|
||||
269
src/modules/PowerOCR/PowerOCR/Helpers/ImageMethods.cs
Normal file
269
src/modules/PowerOCR/PowerOCR/Helpers/ImageMethods.cs
Normal file
@@ -0,0 +1,269 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Windows.Globalization;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Media.Ocr;
|
||||
using BitmapDecoder = Windows.Graphics.Imaging.BitmapDecoder;
|
||||
|
||||
namespace PowerOCR;
|
||||
|
||||
internal class ImageMethods
|
||||
{
|
||||
internal static ImageSource GetWindowBoundsImage(Window passedWindow)
|
||||
{
|
||||
bool isGrabFrame = false;
|
||||
|
||||
DpiScale dpi = VisualTreeHelper.GetDpi(passedWindow);
|
||||
int windowWidth = (int)(passedWindow.ActualWidth * dpi.DpiScaleX);
|
||||
int windowHeight = (int)(passedWindow.ActualHeight * dpi.DpiScaleY);
|
||||
|
||||
System.Windows.Point absPosPoint = passedWindow.GetAbsolutePosition();
|
||||
int thisCorrectedLeft = (int)absPosPoint.X;
|
||||
int thisCorrectedTop = (int)absPosPoint.Y;
|
||||
|
||||
if (isGrabFrame == true)
|
||||
{
|
||||
thisCorrectedLeft += (int)(2 * dpi.DpiScaleX);
|
||||
thisCorrectedTop += (int)(26 * dpi.DpiScaleY);
|
||||
windowWidth -= (int)(4 * dpi.DpiScaleX);
|
||||
windowHeight -= (int)(70 * dpi.DpiScaleY);
|
||||
}
|
||||
|
||||
using Bitmap bmp = new Bitmap(windowWidth, windowHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||
using Graphics g = Graphics.FromImage(bmp);
|
||||
|
||||
g.CopyFromScreen(thisCorrectedLeft, thisCorrectedTop, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
|
||||
return BitmapToImageSource(bmp);
|
||||
}
|
||||
|
||||
internal static async Task<string> GetRegionsText(Window? passedWindow, Rectangle selectedRegion)
|
||||
{
|
||||
using Bitmap bmp = new Bitmap(selectedRegion.Width, selectedRegion.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||
using Graphics g = Graphics.FromImage(bmp);
|
||||
|
||||
System.Windows.Point absPosPoint = passedWindow == null ? default(System.Windows.Point) : passedWindow.GetAbsolutePosition();
|
||||
|
||||
int thisCorrectedLeft = (int)absPosPoint.X + selectedRegion.Left;
|
||||
int thisCorrectedTop = (int)absPosPoint.Y + selectedRegion.Top;
|
||||
|
||||
g.CopyFromScreen(thisCorrectedLeft, thisCorrectedTop, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
|
||||
|
||||
// bmp = PadImage(bmp);
|
||||
string? resultText = await ExtractText(bmp);
|
||||
|
||||
return resultText != null ? resultText.Trim() : string.Empty;
|
||||
}
|
||||
|
||||
internal static async Task<string> GetClickedWord(Window passedWindow, System.Windows.Point clickedPoint)
|
||||
{
|
||||
DpiScale dpi = VisualTreeHelper.GetDpi(passedWindow);
|
||||
Bitmap bmp = new Bitmap((int)(passedWindow.ActualWidth * dpi.DpiScaleX), (int)(passedWindow.ActualHeight * dpi.DpiScaleY), System.Drawing.Imaging.PixelFormat.Format32bppArgb);
|
||||
Graphics g = Graphics.FromImage(bmp);
|
||||
|
||||
System.Windows.Point absPosPoint = passedWindow.GetAbsolutePosition();
|
||||
int thisCorrectedLeft = (int)absPosPoint.X;
|
||||
int thisCorrectedTop = (int)absPosPoint.Y;
|
||||
|
||||
g.CopyFromScreen(thisCorrectedLeft, thisCorrectedTop, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
|
||||
|
||||
System.Windows.Point adjustedPoint = new System.Windows.Point(clickedPoint.X, clickedPoint.Y);
|
||||
|
||||
string resultText = await ExtractText(bmp, adjustedPoint);
|
||||
return resultText.Trim();
|
||||
}
|
||||
|
||||
public static async Task<string> ExtractText(Bitmap bmp, System.Windows.Point? singlePoint = null)
|
||||
{
|
||||
Language? selectedLanguage = GetOCRLanguage();
|
||||
if (selectedLanguage == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
bool isCJKLang = false;
|
||||
|
||||
if (selectedLanguage.LanguageTag.StartsWith("zh", StringComparison.InvariantCultureIgnoreCase) == true)
|
||||
{
|
||||
isCJKLang = true;
|
||||
}
|
||||
else if (selectedLanguage.LanguageTag.StartsWith("ja", StringComparison.InvariantCultureIgnoreCase) == true)
|
||||
{
|
||||
isCJKLang = true;
|
||||
}
|
||||
else if (selectedLanguage.LanguageTag.StartsWith("ko", StringComparison.InvariantCultureIgnoreCase) == true)
|
||||
{
|
||||
isCJKLang = true;
|
||||
}
|
||||
|
||||
XmlLanguage lang = XmlLanguage.GetLanguage(selectedLanguage.LanguageTag);
|
||||
CultureInfo culture = lang.GetEquivalentCulture();
|
||||
|
||||
bool scaleBMP = true;
|
||||
|
||||
if (singlePoint != null
|
||||
|| bmp.Width * 1.5 > OcrEngine.MaxImageDimension)
|
||||
{
|
||||
scaleBMP = false;
|
||||
}
|
||||
|
||||
using Bitmap scaledBitmap = scaleBMP ? ScaleBitmapUniform(bmp, 1.5) : ScaleBitmapUniform(bmp, 1.0);
|
||||
StringBuilder text = new StringBuilder();
|
||||
|
||||
await using (MemoryStream memory = new MemoryStream())
|
||||
{
|
||||
scaledBitmap.Save(memory, ImageFormat.Bmp);
|
||||
memory.Position = 0;
|
||||
BitmapDecoder bmpDecoder = await BitmapDecoder.CreateAsync(memory.AsRandomAccessStream());
|
||||
SoftwareBitmap softwareBmp = await bmpDecoder.GetSoftwareBitmapAsync();
|
||||
|
||||
OcrEngine ocrEngine = OcrEngine.TryCreateFromLanguage(selectedLanguage);
|
||||
OcrResult ocrResult = await ocrEngine.RecognizeAsync(softwareBmp);
|
||||
|
||||
if (singlePoint == null)
|
||||
{
|
||||
foreach (OcrLine line in ocrResult.Lines)
|
||||
{
|
||||
text.AppendLine(line.Text);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Windows.Foundation.Point fPoint = new Windows.Foundation.Point(singlePoint.Value.X, singlePoint.Value.Y);
|
||||
foreach (OcrLine ocrLine in ocrResult.Lines)
|
||||
{
|
||||
foreach (OcrWord ocrWord in ocrLine.Words)
|
||||
{
|
||||
if (ocrWord.BoundingRect.Contains(fPoint))
|
||||
{
|
||||
_ = text.Append(ocrWord.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (culture.TextInfo.IsRightToLeft)
|
||||
{
|
||||
string[] textListLines = text.ToString().Split(new char[] { '\n', '\r' });
|
||||
|
||||
_ = text.Clear();
|
||||
foreach (string textLine in textListLines)
|
||||
{
|
||||
List<string> wordArray = textLine.Split().ToList();
|
||||
wordArray.Reverse();
|
||||
_ = isCJKLang == true ? text.Append(string.Join(string.Empty, wordArray)) : text.Append(string.Join(' ', wordArray));
|
||||
|
||||
if (textLine.Length > 0)
|
||||
{
|
||||
_ = text.Append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
return text.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return text.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public static Bitmap ScaleBitmapUniform(Bitmap passedBitmap, double scale)
|
||||
{
|
||||
using MemoryStream memory = new MemoryStream();
|
||||
passedBitmap.Save(memory, ImageFormat.Bmp);
|
||||
memory.Position = 0;
|
||||
BitmapImage bitmapimage = new BitmapImage();
|
||||
bitmapimage.BeginInit();
|
||||
bitmapimage.StreamSource = memory;
|
||||
bitmapimage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapimage.EndInit();
|
||||
bitmapimage.Freeze();
|
||||
TransformedBitmap transformedBmp = new TransformedBitmap();
|
||||
transformedBmp.BeginInit();
|
||||
transformedBmp.Source = bitmapimage;
|
||||
transformedBmp.Transform = new ScaleTransform(scale, scale);
|
||||
transformedBmp.EndInit();
|
||||
transformedBmp.Freeze();
|
||||
return BitmapSourceToBitmap(transformedBmp);
|
||||
}
|
||||
|
||||
public static Bitmap BitmapSourceToBitmap(BitmapSource source)
|
||||
{
|
||||
Bitmap bmp = new Bitmap(
|
||||
source.PixelWidth,
|
||||
source.PixelHeight,
|
||||
System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
|
||||
BitmapData data = bmp.LockBits(
|
||||
new Rectangle(System.Drawing.Point.Empty, bmp.Size),
|
||||
ImageLockMode.WriteOnly,
|
||||
System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
|
||||
source.CopyPixels(
|
||||
Int32Rect.Empty,
|
||||
data.Scan0,
|
||||
data.Height * data.Stride,
|
||||
data.Stride);
|
||||
bmp.UnlockBits(data);
|
||||
return bmp;
|
||||
}
|
||||
|
||||
internal static BitmapImage BitmapToImageSource(Bitmap bitmap)
|
||||
{
|
||||
using MemoryStream memory = new MemoryStream();
|
||||
bitmap.Save(memory, ImageFormat.Bmp);
|
||||
memory.Position = 0;
|
||||
BitmapImage bitmapimage = new BitmapImage();
|
||||
bitmapimage.BeginInit();
|
||||
bitmapimage.StreamSource = memory;
|
||||
bitmapimage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapimage.EndInit();
|
||||
bitmapimage.Freeze();
|
||||
|
||||
return bitmapimage;
|
||||
}
|
||||
|
||||
public static Language? GetOCRLanguage()
|
||||
{
|
||||
// use currently selected Language
|
||||
string inputLang = InputLanguageManager.Current.CurrentInputLanguage.Name;
|
||||
|
||||
Language? selectedLanguage = new Language(inputLang);
|
||||
List<Language> possibleOcrLanguages = OcrEngine.AvailableRecognizerLanguages.ToList();
|
||||
|
||||
if (possibleOcrLanguages.Count < 1)
|
||||
{
|
||||
MessageBox.Show("No possible OCR languages are installed.", "Text Grab");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (possibleOcrLanguages.All(l => l.LanguageTag != selectedLanguage.LanguageTag))
|
||||
{
|
||||
List<Language>? similarLanguages = possibleOcrLanguages.Where(
|
||||
la => la.AbbreviatedName == selectedLanguage.AbbreviatedName).ToList();
|
||||
|
||||
if (similarLanguages != null)
|
||||
{
|
||||
selectedLanguage = similarLanguages.Count > 0
|
||||
? similarLanguages.FirstOrDefault()
|
||||
: possibleOcrLanguages.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
return selectedLanguage;
|
||||
}
|
||||
}
|
||||
79
src/modules/PowerOCR/PowerOCR/Helpers/Logger.cs
Normal file
79
src/modules/PowerOCR/PowerOCR/Helpers/Logger.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using interop;
|
||||
|
||||
namespace PowerOCR.Helpers
|
||||
{
|
||||
public static class Logger
|
||||
{
|
||||
private static readonly IFileSystem _fileSystem = new FileSystem();
|
||||
private static readonly string ApplicationLogPath = Path.Combine(Constants.AppDataPath(), "PowerOCR\\Logs");
|
||||
|
||||
static Logger()
|
||||
{
|
||||
if (!_fileSystem.Directory.Exists(ApplicationLogPath))
|
||||
{
|
||||
_fileSystem.Directory.CreateDirectory(ApplicationLogPath);
|
||||
}
|
||||
|
||||
// Using InvariantCulture since this is used for a log file name
|
||||
var logFilePath = _fileSystem.Path.Combine(ApplicationLogPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".txt");
|
||||
|
||||
Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));
|
||||
|
||||
Trace.AutoFlush = true;
|
||||
}
|
||||
|
||||
public static void LogError(string message)
|
||||
{
|
||||
Log(message, "ERROR");
|
||||
}
|
||||
|
||||
public static void LogError(string message, Exception ex)
|
||||
{
|
||||
Log(
|
||||
message + Environment.NewLine +
|
||||
ex?.Message + Environment.NewLine +
|
||||
"Inner exception: " + Environment.NewLine +
|
||||
ex?.InnerException?.Message + Environment.NewLine +
|
||||
"Stack trace: " + Environment.NewLine +
|
||||
ex?.StackTrace,
|
||||
"ERROR");
|
||||
}
|
||||
|
||||
public static void LogWarning(string message)
|
||||
{
|
||||
Log(message, "WARNING");
|
||||
}
|
||||
|
||||
public static void LogInfo(string message)
|
||||
{
|
||||
Log(message, "INFO");
|
||||
}
|
||||
|
||||
private static void Log(string message, string type)
|
||||
{
|
||||
Trace.WriteLine(type + ": " + DateTime.Now.TimeOfDay);
|
||||
Trace.Indent();
|
||||
Trace.WriteLine(GetCallerInfo());
|
||||
Trace.WriteLine(message);
|
||||
Trace.Unindent();
|
||||
}
|
||||
|
||||
private static string GetCallerInfo()
|
||||
{
|
||||
StackTrace stackTrace = new StackTrace();
|
||||
|
||||
var methodName = stackTrace.GetFrame(3)?.GetMethod();
|
||||
var className = methodName?.DeclaringType?.Name;
|
||||
return "[Method]: " + methodName?.Name + " [Class]: " + className;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/modules/PowerOCR/PowerOCR/Helpers/NativeEventWaiter.cs
Normal file
29
src/modules/PowerOCR/PowerOCR/Helpers/NativeEventWaiter.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
|
||||
namespace PowerOCR.Helpers
|
||||
{
|
||||
public static class NativeEventWaiter
|
||||
{
|
||||
public static void WaitForEventLoop(string eventName, Action callback)
|
||||
{
|
||||
new Thread(() =>
|
||||
{
|
||||
var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName);
|
||||
while (true)
|
||||
{
|
||||
if (eventHandle.WaitOne())
|
||||
{
|
||||
Logger.LogInfo($"Successfully waited for {eventName}");
|
||||
Application.Current.Dispatcher.Invoke(callback);
|
||||
}
|
||||
}
|
||||
}).Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
129
src/modules/PowerOCR/PowerOCR/Helpers/OSInterop.cs
Normal file
129
src/modules/PowerOCR/PowerOCR/Helpers/OSInterop.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PowerOCR;
|
||||
|
||||
public static class OSInterop
|
||||
{
|
||||
#pragma warning disable SA1310 // Field names should not contain underscore
|
||||
public const int WH_KEYBOARD_LL = 13;
|
||||
public const int VK_SHIFT = 0x10;
|
||||
public const int VK_CONTROL = 0x11;
|
||||
public const int VK_MENU = 0x12;
|
||||
public const int VK_LWIN = 0x5B;
|
||||
public const int VK_RWIN = 0x5C;
|
||||
public const int VK_ESCAPE = 0x1B;
|
||||
public const int WM_HOTKEY = 0x0312;
|
||||
public const int WM_KEYDOWN = 0x0100;
|
||||
public const int WM_KEYUP = 0x0101;
|
||||
|
||||
#pragma warning disable CA1401 // P/Invokes should not be visible
|
||||
[DllImport("user32.dll")]
|
||||
public static extern int GetSystemMetrics(int smIndex);
|
||||
|
||||
public const int SM_CMONITORS = 80;
|
||||
|
||||
#pragma warning restore SA1310 // Field names should not contain underscore
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool SystemParametersInfo(int nAction, int nParam, ref RECT rc, int nUpdate);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||
public static extern bool GetMonitorInfo(HandleRef hmonitor, [In, Out] MONITORINFOEX info);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr MonitorFromWindow(HandleRef handle, int flags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool ClipCursor(ref RECT lpRect);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern bool ClipCursor([In] IntPtr lpRect);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
|
||||
internal static extern bool FreeLibrary(IntPtr hModule);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
internal static extern bool UnregisterHotKey(IntPtr hWnd, int id);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
|
||||
internal static extern bool UnhookWindowsHookEx(IntPtr idHook);
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||
internal static extern IntPtr LoadLibrary(string lpFileName);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
|
||||
internal static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
|
||||
internal static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
|
||||
internal static extern short GetAsyncKeyState(int vKey);
|
||||
|
||||
public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
|
||||
#pragma warning restore CA1401 // P/Invokes should not be visible
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct LowLevelKeyboardInputEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// A virtual-key code. The code must be a value in the range 1 to 254.
|
||||
/// </summary>
|
||||
public int VirtualCode;
|
||||
|
||||
/// <summary>
|
||||
/// A hardware scan code for the key.
|
||||
/// </summary>
|
||||
public int HardwareScanCode;
|
||||
|
||||
/// <summary>
|
||||
/// The extended-key flag, event-injected Flags, context code, and transition-state flag. This member is specified as follows. An application can use the following values to test the keystroke Flags. Testing LLKHF_INJECTED (bit 4) will tell you whether the event was injected. If it was, then testing LLKHF_LOWER_IL_INJECTED (bit 1) will tell you whether or not the event was injected from a process running at lower integrity level.
|
||||
/// </summary>
|
||||
public int Flags;
|
||||
|
||||
/// <summary>
|
||||
/// The time stamp for this message, equivalent to what GetMessageTime would return for this message.
|
||||
/// </summary>
|
||||
public int TimeStamp;
|
||||
|
||||
/// <summary>
|
||||
/// Additional information associated with the message.
|
||||
/// </summary>
|
||||
public IntPtr AdditionalInformation;
|
||||
}
|
||||
|
||||
public struct RECT
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
|
||||
public int Width => Right - Left;
|
||||
|
||||
public int Height => Bottom - Top;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Auto)]
|
||||
public class MONITORINFOEX
|
||||
{
|
||||
public int CbSize { get; set; } = Marshal.SizeOf(typeof(MONITORINFOEX));
|
||||
|
||||
public RECT RcMonitor { get; set; }
|
||||
|
||||
public RECT RcWork { get; set; }
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
|
||||
private char[] szDevice = new char[32];
|
||||
|
||||
public int DwFlags { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace PowerOCR.Helpers;
|
||||
|
||||
[Export(typeof(IThrottledActionInvoker))]
|
||||
public sealed class ThrottledActionInvoker : IThrottledActionInvoker
|
||||
{
|
||||
private object _invokerLock = new object();
|
||||
private Action? _actionToRun;
|
||||
|
||||
private DispatcherTimer _timer;
|
||||
|
||||
public ThrottledActionInvoker()
|
||||
{
|
||||
_timer = new DispatcherTimer();
|
||||
_timer.Tick += Timer_Tick;
|
||||
}
|
||||
|
||||
public void ScheduleAction(Action action, int milliseconds)
|
||||
{
|
||||
lock (_invokerLock)
|
||||
{
|
||||
if (_timer.IsEnabled)
|
||||
{
|
||||
_timer.Stop();
|
||||
}
|
||||
|
||||
_actionToRun = action;
|
||||
_timer.Interval = new TimeSpan(0, 0, 0, 0, milliseconds);
|
||||
|
||||
_timer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void Timer_Tick(object? sender, EventArgs? e)
|
||||
{
|
||||
lock (_invokerLock)
|
||||
{
|
||||
_timer.Stop();
|
||||
_actionToRun?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
40
src/modules/PowerOCR/PowerOCR/Helpers/WPFExtensionMethods.cs
Normal file
40
src/modules/PowerOCR/PowerOCR/Helpers/WPFExtensionMethods.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
|
||||
namespace PowerOCR;
|
||||
|
||||
public static class WPFExtensionMethods
|
||||
{
|
||||
public static Point GetAbsolutePosition(this Window w)
|
||||
{
|
||||
if (w.WindowState != WindowState.Maximized)
|
||||
{
|
||||
return new Point(w.Left, w.Top);
|
||||
}
|
||||
|
||||
Int32Rect r;
|
||||
bool multiMonitorSupported = OSInterop.GetSystemMetrics(OSInterop.SM_CMONITORS) != 0;
|
||||
if (!multiMonitorSupported)
|
||||
{
|
||||
OSInterop.RECT rc = default(OSInterop.RECT);
|
||||
OSInterop.SystemParametersInfo(48, 0, ref rc, 0);
|
||||
r = new Int32Rect(rc.Left, rc.Top, rc.Width, rc.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowInteropHelper helper = new WindowInteropHelper(w);
|
||||
IntPtr hmonitor = OSInterop.MonitorFromWindow(new HandleRef(null, helper.EnsureHandle()), 2);
|
||||
OSInterop.MONITORINFOEX info = new OSInterop.MONITORINFOEX();
|
||||
OSInterop.GetMonitorInfo(new HandleRef(null, hmonitor), info);
|
||||
r = new Int32Rect(info.RcMonitor.Left, info.RcMonitor.Top, info.RcMonitor.Width, info.RcMonitor.Height);
|
||||
}
|
||||
|
||||
return new Point(r.X, r.Y);
|
||||
}
|
||||
}
|
||||
113
src/modules/PowerOCR/PowerOCR/Helpers/WindowUtilities.cs
Normal file
113
src/modules/PowerOCR/PowerOCR/Helpers/WindowUtilities.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using PowerOCR.Helpers;
|
||||
|
||||
namespace PowerOCR.Utilities;
|
||||
|
||||
public static class WindowUtilities
|
||||
{
|
||||
public static void LaunchOCROverlayOnEveryScreen()
|
||||
{
|
||||
if (IsOCROverlayCreated())
|
||||
{
|
||||
Logger.LogWarning("Tired to launch the overlay but it was already created.");
|
||||
return;
|
||||
}
|
||||
|
||||
Screen[] allScreens = Screen.AllScreens;
|
||||
WindowCollection allWindows = System.Windows.Application.Current.Windows;
|
||||
|
||||
List<OCROverlay> allFullscreenGrab = new List<OCROverlay>();
|
||||
|
||||
foreach (Screen screen in allScreens)
|
||||
{
|
||||
bool screenHasWindow = true;
|
||||
|
||||
foreach (Window window in allWindows)
|
||||
{
|
||||
System.Drawing.Point windowCenter =
|
||||
new System.Drawing.Point(
|
||||
(int)(window.Left + (window.Width / 2)),
|
||||
(int)(window.Top + (window.Height / 2)));
|
||||
screenHasWindow = screen.Bounds.Contains(windowCenter);
|
||||
|
||||
// if (window is EditTextWindow)
|
||||
// isEditWindowOpen = true;
|
||||
}
|
||||
|
||||
if (allWindows.Count < 1)
|
||||
{
|
||||
screenHasWindow = false;
|
||||
}
|
||||
|
||||
OCROverlay overlay = new OCROverlay()
|
||||
{
|
||||
WindowStartupLocation = WindowStartupLocation.Manual,
|
||||
Width = 200,
|
||||
Height = 200,
|
||||
WindowState = WindowState.Normal,
|
||||
};
|
||||
|
||||
if (screen.WorkingArea.Left >= 0)
|
||||
{
|
||||
overlay.Left = screen.WorkingArea.Left;
|
||||
}
|
||||
else
|
||||
{
|
||||
overlay.Left = screen.WorkingArea.Left + (screen.WorkingArea.Width / 2);
|
||||
}
|
||||
|
||||
if (screen.WorkingArea.Top >= 0)
|
||||
{
|
||||
overlay.Top = screen.WorkingArea.Top;
|
||||
}
|
||||
else
|
||||
{
|
||||
overlay.Top = screen.WorkingArea.Top + (screen.WorkingArea.Height / 2);
|
||||
}
|
||||
|
||||
overlay.Show();
|
||||
overlay.Activate();
|
||||
allFullscreenGrab.Add(overlay);
|
||||
}
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new PowerOCR.Telemetry.PowerOCRLaunchOverlayEvent());
|
||||
}
|
||||
|
||||
internal static bool IsOCROverlayCreated()
|
||||
{
|
||||
WindowCollection allWindows = System.Windows.Application.Current.Windows;
|
||||
|
||||
foreach (Window window in allWindows)
|
||||
{
|
||||
if (window is OCROverlay overlay)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static void CloseAllOCROverlays()
|
||||
{
|
||||
WindowCollection allWindows = System.Windows.Application.Current.Windows;
|
||||
|
||||
foreach (Window window in allWindows)
|
||||
{
|
||||
if (window is OCROverlay overlay)
|
||||
{
|
||||
overlay.Close();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Decide when to close the process
|
||||
// System.Windows.Application.Current.Shutdown();
|
||||
}
|
||||
}
|
||||
32
src/modules/PowerOCR/PowerOCR/Keyboard/EventMonitor.cs
Normal file
32
src/modules/PowerOCR/PowerOCR/Keyboard/EventMonitor.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Windows.Interop;
|
||||
using interop;
|
||||
using PowerOCR.Helpers;
|
||||
using PowerOCR.Utilities;
|
||||
|
||||
namespace PowerOCR.Keyboard
|
||||
{
|
||||
/// <summary>
|
||||
/// This class handles the interaction model when running from PowerToys Run.
|
||||
/// Handles activation through the event sent by the runner.
|
||||
/// </summary>
|
||||
internal class EventMonitor
|
||||
{
|
||||
public EventMonitor()
|
||||
{
|
||||
NativeEventWaiter.WaitForEventLoop(Constants.ShowPowerOCRSharedEvent(), StartOCRSession);
|
||||
}
|
||||
|
||||
public void StartOCRSession()
|
||||
{
|
||||
if (!WindowUtilities.IsOCROverlayCreated())
|
||||
{
|
||||
WindowUtilities.LaunchOCROverlayOnEveryScreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
117
src/modules/PowerOCR/PowerOCR/Keyboard/GlobalKeyboardHook.cs
Normal file
117
src/modules/PowerOCR/PowerOCR/Keyboard/GlobalKeyboardHook.cs
Normal file
@@ -0,0 +1,117 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using static PowerOCR.OSInterop;
|
||||
|
||||
namespace PowerOCR.Keyboard;
|
||||
|
||||
internal class GlobalKeyboardHook : IDisposable
|
||||
{
|
||||
private IntPtr _windowsHookHandle;
|
||||
private IntPtr _user32LibraryHandle;
|
||||
private HookProc? _hookProc;
|
||||
|
||||
public GlobalKeyboardHook()
|
||||
{
|
||||
_windowsHookHandle = IntPtr.Zero;
|
||||
_user32LibraryHandle = IntPtr.Zero;
|
||||
_hookProc = LowLevelKeyboardProc; // we must keep alive _hookProc, because GC is not aware about SetWindowsHookEx behaviour.
|
||||
|
||||
_user32LibraryHandle = LoadLibrary("User32");
|
||||
if (_user32LibraryHandle == IntPtr.Zero)
|
||||
{
|
||||
int errorCode = Marshal.GetLastWin32Error();
|
||||
throw new Win32Exception(errorCode, $"Failed to load library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
|
||||
}
|
||||
|
||||
_windowsHookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, _hookProc, _user32LibraryHandle, 0);
|
||||
if (_windowsHookHandle == IntPtr.Zero)
|
||||
{
|
||||
int errorCode = Marshal.GetLastWin32Error();
|
||||
throw new Win32Exception(errorCode, $"Failed to adjust keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
|
||||
}
|
||||
}
|
||||
|
||||
internal event EventHandler<GlobalKeyboardHookEventArgs>? KeyboardPressed;
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// because we can unhook only in the same thread, not in garbage collector thread
|
||||
if (_windowsHookHandle != IntPtr.Zero)
|
||||
{
|
||||
if (!UnhookWindowsHookEx(_windowsHookHandle))
|
||||
{
|
||||
int errorCode = Marshal.GetLastWin32Error();
|
||||
throw new Win32Exception(errorCode, $"Failed to remove keyboard hooks for '{Process.GetCurrentProcess().ProcessName}'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
|
||||
}
|
||||
|
||||
_windowsHookHandle = IntPtr.Zero;
|
||||
|
||||
// ReSharper disable once DelegateSubtraction
|
||||
_hookProc -= LowLevelKeyboardProc;
|
||||
}
|
||||
}
|
||||
|
||||
if (_user32LibraryHandle != IntPtr.Zero)
|
||||
{
|
||||
// reduces reference to library by 1.
|
||||
if (!FreeLibrary(_user32LibraryHandle))
|
||||
{
|
||||
int errorCode = Marshal.GetLastWin32Error();
|
||||
throw new Win32Exception(errorCode, $"Failed to unload library 'User32.dll'. Error {errorCode}: {new Win32Exception(Marshal.GetLastWin32Error()).Message}.");
|
||||
}
|
||||
|
||||
_user32LibraryHandle = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
~GlobalKeyboardHook()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public enum KeyboardState
|
||||
{
|
||||
KeyDown = 0x0100,
|
||||
KeyUp = 0x0101,
|
||||
SysKeyDown = 0x0104,
|
||||
SysKeyUp = 0x0105,
|
||||
}
|
||||
|
||||
private IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
bool fEatKeyStroke = false;
|
||||
|
||||
var wparamTyped = wParam.ToInt32();
|
||||
if (Enum.IsDefined(typeof(KeyboardState), wparamTyped))
|
||||
{
|
||||
object? o = Marshal.PtrToStructure(lParam, typeof(LowLevelKeyboardInputEvent));
|
||||
if (o is not null)
|
||||
{
|
||||
LowLevelKeyboardInputEvent p = (LowLevelKeyboardInputEvent)o;
|
||||
|
||||
var eventArguments = new GlobalKeyboardHookEventArgs(p, (KeyboardState)wparamTyped);
|
||||
|
||||
EventHandler<GlobalKeyboardHookEventArgs>? handler = KeyboardPressed;
|
||||
handler?.Invoke(this, eventArguments);
|
||||
|
||||
fEatKeyStroke = eventArguments.Handled;
|
||||
}
|
||||
}
|
||||
|
||||
return fEatKeyStroke ? (IntPtr)1 : CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.ComponentModel;
|
||||
using static PowerOCR.OSInterop;
|
||||
|
||||
namespace PowerOCR.Keyboard;
|
||||
|
||||
internal class GlobalKeyboardHookEventArgs : HandledEventArgs
|
||||
{
|
||||
internal GlobalKeyboardHook.KeyboardState KeyboardState { get; private set; }
|
||||
|
||||
internal LowLevelKeyboardInputEvent KeyboardData { get; private set; }
|
||||
|
||||
internal GlobalKeyboardHookEventArgs(
|
||||
LowLevelKeyboardInputEvent keyboardData,
|
||||
GlobalKeyboardHook.KeyboardState keyboardState)
|
||||
{
|
||||
KeyboardData = keyboardData;
|
||||
KeyboardState = keyboardState;
|
||||
}
|
||||
}
|
||||
172
src/modules/PowerOCR/PowerOCR/Keyboard/KeyboardMonitor.cs
Normal file
172
src/modules/PowerOCR/PowerOCR/Keyboard/KeyboardMonitor.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Windows.Input;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
|
||||
using PowerOCR.Settings;
|
||||
using PowerOCR.Utilities;
|
||||
using static PowerOCR.OSInterop;
|
||||
|
||||
namespace PowerOCR.Keyboard;
|
||||
|
||||
[Export(typeof(KeyboardMonitor))]
|
||||
public class KeyboardMonitor : IDisposable
|
||||
{
|
||||
private readonly IUserSettings _userSettings;
|
||||
private List<string> _previouslyPressedKeys = new List<string>();
|
||||
|
||||
private List<string> _activationKeys = new List<string>();
|
||||
private GlobalKeyboardHook? _keyboardHook;
|
||||
private bool disposedValue;
|
||||
private bool _activationShortcutPressed;
|
||||
|
||||
[ImportingConstructor]
|
||||
public KeyboardMonitor(IUserSettings userSettings)
|
||||
{
|
||||
_userSettings = userSettings;
|
||||
_userSettings.ActivationShortcut.PropertyChanged -= ActivationShortcut_PropertyChanged;
|
||||
_userSettings.ActivationShortcut.PropertyChanged += ActivationShortcut_PropertyChanged;
|
||||
SetActivationKeys();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
_keyboardHook = new GlobalKeyboardHook();
|
||||
_keyboardHook.KeyboardPressed += Hook_KeyboardPressed;
|
||||
}
|
||||
|
||||
private void SetActivationKeys()
|
||||
{
|
||||
_activationKeys.Clear();
|
||||
|
||||
if (!string.IsNullOrEmpty(_userSettings.ActivationShortcut.Value))
|
||||
{
|
||||
var keys = _userSettings.ActivationShortcut.Value.Split('+');
|
||||
foreach (var key in keys)
|
||||
{
|
||||
_activationKeys.Add(key.Trim());
|
||||
}
|
||||
|
||||
_activationKeys.Sort();
|
||||
}
|
||||
}
|
||||
|
||||
private void ActivationShortcut_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs? e)
|
||||
{
|
||||
SetActivationKeys();
|
||||
}
|
||||
|
||||
private void Hook_KeyboardPressed(object? sender, GlobalKeyboardHookEventArgs? e)
|
||||
{
|
||||
if (e is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var currentlyPressedKeys = new List<string>();
|
||||
var virtualCode = e.KeyboardData.VirtualCode;
|
||||
|
||||
var name = Helper.GetKeyName((uint)virtualCode);
|
||||
|
||||
// If the last key pressed is a modifier key, then currentlyPressedKeys cannot possibly match with _activationKeys
|
||||
// because _activationKeys contains exactly 1 non-modifier key. Hence, there's no need to check if `name` is a
|
||||
// modifier key or to do any additional processing on it.
|
||||
if (e.KeyboardState == GlobalKeyboardHook.KeyboardState.KeyDown || e.KeyboardState == GlobalKeyboardHook.KeyboardState.SysKeyDown)
|
||||
{
|
||||
// Check pressed modifier keys.
|
||||
AddModifierKeys(currentlyPressedKeys);
|
||||
|
||||
currentlyPressedKeys.Add(name);
|
||||
}
|
||||
|
||||
currentlyPressedKeys.Sort();
|
||||
|
||||
if (currentlyPressedKeys.Count == 0 && _previouslyPressedKeys.Count != 0)
|
||||
{
|
||||
// no keys pressed, we can enable activation shortcut again
|
||||
_activationShortcutPressed = false;
|
||||
}
|
||||
|
||||
_previouslyPressedKeys = currentlyPressedKeys;
|
||||
|
||||
if (ArraysAreSame(currentlyPressedKeys, _activationKeys))
|
||||
{
|
||||
// avoid triggering this action multiple times as this will be called nonstop while keys are pressed
|
||||
if (!_activationShortcutPressed)
|
||||
{
|
||||
_activationShortcutPressed = true;
|
||||
e.Handled = true;
|
||||
WindowUtilities.LaunchOCROverlayOnEveryScreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ArraysAreSame(List<string> first, List<string> second)
|
||||
{
|
||||
if (first.Count != second.Count || (first.Count == 0 && second.Count == 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < first.Count; i++)
|
||||
{
|
||||
if (first[i] != second[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void AddModifierKeys(List<string> currentlyPressedKeys)
|
||||
{
|
||||
if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0)
|
||||
{
|
||||
currentlyPressedKeys.Add("Shift");
|
||||
}
|
||||
|
||||
if ((GetAsyncKeyState(VK_CONTROL) & 0x8000) != 0)
|
||||
{
|
||||
currentlyPressedKeys.Add("Ctrl");
|
||||
}
|
||||
|
||||
if ((GetAsyncKeyState(VK_MENU) & 0x8000) != 0)
|
||||
{
|
||||
currentlyPressedKeys.Add("Alt");
|
||||
}
|
||||
|
||||
if ((GetAsyncKeyState(VK_LWIN) & 0x8000) != 0 || (GetAsyncKeyState(VK_RWIN) & 0x8000) != 0)
|
||||
{
|
||||
currentlyPressedKeys.Add("Win");
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// TODO: dispose managed state (managed objects)
|
||||
_keyboardHook?.Dispose();
|
||||
}
|
||||
|
||||
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
|
||||
// TODO: set large fields to null
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
_userSettings.ActivationShortcut.PropertyChanged -= ActivationShortcut_PropertyChanged;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
55
src/modules/PowerOCR/PowerOCR/OCROverlay.xaml
Normal file
55
src/modules/PowerOCR/PowerOCR/OCROverlay.xaml
Normal file
@@ -0,0 +1,55 @@
|
||||
<Window
|
||||
x:Class="PowerOCR.OCROverlay"
|
||||
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:local="clr-namespace:PowerOCR"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
Title="PowerOCR"
|
||||
Width="800"
|
||||
Height="450"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent"
|
||||
Loaded="Window_Loaded"
|
||||
Unloaded="Window_Unloaded"
|
||||
ResizeMode="NoResize"
|
||||
Topmost="True"
|
||||
WindowStyle="None"
|
||||
mc:Ignorable="d">
|
||||
<Grid>
|
||||
<Viewbox>
|
||||
<Image x:Name="BackgroundImage" Stretch="UniformToFill" />
|
||||
</Viewbox>
|
||||
<Canvas
|
||||
Name="RegionClickCanvas"
|
||||
Cursor="Cross"
|
||||
MouseDown="RegionClickCanvas_MouseDown"
|
||||
MouseMove="RegionClickCanvas_MouseMove"
|
||||
MouseUp="RegionClickCanvas_MouseUp">
|
||||
<Canvas.Clip>
|
||||
<CombinedGeometry GeometryCombineMode="Exclude">
|
||||
<CombinedGeometry.Geometry1>
|
||||
<RectangleGeometry x:Name="FullWindow" />
|
||||
</CombinedGeometry.Geometry1>
|
||||
<CombinedGeometry.Geometry2>
|
||||
<RectangleGeometry x:Name="clippingGeometry" />
|
||||
</CombinedGeometry.Geometry2>
|
||||
</CombinedGeometry>
|
||||
</Canvas.Clip>
|
||||
<Canvas.Background>
|
||||
<SolidColorBrush
|
||||
x:Name="BackgroundBrush"
|
||||
Opacity="0"
|
||||
Color="Black" />
|
||||
</Canvas.Background>
|
||||
<Canvas.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem
|
||||
Name="CancelMenuItem"
|
||||
Click="CancelMenuItem_Click"
|
||||
Header="Cancel" />
|
||||
</ContextMenu>
|
||||
</Canvas.ContextMenu>
|
||||
</Canvas>
|
||||
</Grid>
|
||||
</Window>
|
||||
284
src/modules/PowerOCR/PowerOCR/OCROverlay.xaml.cs
Normal file
284
src/modules/PowerOCR/PowerOCR/OCROverlay.xaml.cs
Normal file
@@ -0,0 +1,284 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using PowerOCR.Helpers;
|
||||
using PowerOCR.Utilities;
|
||||
|
||||
namespace PowerOCR;
|
||||
|
||||
/// <summary>
|
||||
/// Interaction logic for MainWindow.xaml
|
||||
/// </summary>
|
||||
public partial class OCROverlay : Window
|
||||
{
|
||||
private bool isShiftDown;
|
||||
private Point clickedPoint;
|
||||
private Point shiftPoint;
|
||||
|
||||
private bool IsSelecting { get; set; }
|
||||
|
||||
private Border selectBorder = new Border();
|
||||
|
||||
private DpiScale? dpiScale;
|
||||
|
||||
private Point GetMousePos() => PointToScreen(Mouse.GetPosition(this));
|
||||
|
||||
private System.Windows.Forms.Screen? CurrentScreen
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private double selectLeft;
|
||||
private double selectTop;
|
||||
|
||||
private double xShiftDelta;
|
||||
private double yShiftDelta;
|
||||
|
||||
public OCROverlay()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WindowState = WindowState.Maximized;
|
||||
FullWindow.Rect = new Rect(0, 0, Width, Height);
|
||||
KeyDown += MainWindow_KeyDown;
|
||||
KeyUp += MainWindow_KeyUp;
|
||||
|
||||
BackgroundImage.Source = ImageMethods.GetWindowBoundsImage(this);
|
||||
BackgroundBrush.Opacity = 0.4;
|
||||
}
|
||||
|
||||
private void Window_Unloaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
BackgroundImage.Source = null;
|
||||
BackgroundImage.UpdateLayout();
|
||||
|
||||
CurrentScreen = null;
|
||||
dpiScale = null;
|
||||
|
||||
KeyDown -= MainWindow_KeyDown;
|
||||
KeyUp -= MainWindow_KeyUp;
|
||||
|
||||
Loaded -= Window_Loaded;
|
||||
Unloaded -= Window_Unloaded;
|
||||
|
||||
RegionClickCanvas.MouseDown -= RegionClickCanvas_MouseDown;
|
||||
RegionClickCanvas.MouseUp -= RegionClickCanvas_MouseUp;
|
||||
RegionClickCanvas.MouseMove -= RegionClickCanvas_MouseMove;
|
||||
|
||||
CancelMenuItem.Click -= CancelMenuItem_Click;
|
||||
}
|
||||
|
||||
private void MainWindow_KeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.LeftShift:
|
||||
isShiftDown = false;
|
||||
clickedPoint = new Point(clickedPoint.X + xShiftDelta, clickedPoint.Y + yShiftDelta);
|
||||
break;
|
||||
case Key.RightShift:
|
||||
isShiftDown = false;
|
||||
clickedPoint = new Point(clickedPoint.X + xShiftDelta, clickedPoint.Y + yShiftDelta);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void MainWindow_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Escape:
|
||||
WindowUtilities.CloseAllOCROverlays();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void RegionClickCanvas_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.RightButton == MouseButtonState.Pressed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsSelecting = true;
|
||||
RegionClickCanvas.CaptureMouse();
|
||||
|
||||
CursorClipper.ClipCursor(this);
|
||||
clickedPoint = e.GetPosition(this);
|
||||
selectBorder.Height = 1;
|
||||
selectBorder.Width = 1;
|
||||
|
||||
dpiScale = VisualTreeHelper.GetDpi(this);
|
||||
|
||||
try
|
||||
{
|
||||
RegionClickCanvas.Children.Remove(selectBorder);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
selectBorder.BorderThickness = new Thickness(2);
|
||||
Color borderColor = Color.FromArgb(255, 40, 118, 126);
|
||||
selectBorder.BorderBrush = new SolidColorBrush(borderColor);
|
||||
_ = RegionClickCanvas.Children.Add(selectBorder);
|
||||
Canvas.SetLeft(selectBorder, clickedPoint.X);
|
||||
Canvas.SetTop(selectBorder, clickedPoint.Y);
|
||||
|
||||
var screens = System.Windows.Forms.Screen.AllScreens;
|
||||
System.Drawing.Point formsPoint = new System.Drawing.Point((int)clickedPoint.X, (int)clickedPoint.Y);
|
||||
foreach (var scr in screens)
|
||||
{
|
||||
if (scr.Bounds.Contains(formsPoint))
|
||||
{
|
||||
CurrentScreen = scr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RegionClickCanvas_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!IsSelecting)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Point movingPoint = e.GetPosition(this);
|
||||
|
||||
if (System.Windows.Input.Keyboard.Modifiers == ModifierKeys.Shift)
|
||||
{
|
||||
if (!isShiftDown)
|
||||
{
|
||||
shiftPoint = movingPoint;
|
||||
selectLeft = Canvas.GetLeft(selectBorder);
|
||||
selectTop = Canvas.GetTop(selectBorder);
|
||||
}
|
||||
|
||||
isShiftDown = true;
|
||||
xShiftDelta = movingPoint.X - shiftPoint.X;
|
||||
yShiftDelta = movingPoint.Y - shiftPoint.Y;
|
||||
|
||||
double leftValue = selectLeft + xShiftDelta;
|
||||
double topValue = selectTop + yShiftDelta;
|
||||
|
||||
if (CurrentScreen is not null && dpiScale is not null)
|
||||
{
|
||||
double currentScreenLeft = CurrentScreen.Bounds.Left; // Should always be 0
|
||||
double currentScreenRight = CurrentScreen.Bounds.Right / dpiScale.Value.DpiScaleX;
|
||||
double currentScreenTop = CurrentScreen.Bounds.Top; // Should always be 0
|
||||
double currentScreenBottom = CurrentScreen.Bounds.Bottom / dpiScale.Value.DpiScaleY;
|
||||
|
||||
// this is giving issues on different monitors
|
||||
// leftValue = Math.Clamp(leftValue, currentScreenLeft, currentScreenRight - selectBorder.Width);
|
||||
// topValue = Math.Clamp(topValue, currentScreenTop, currentScreenBottom - selectBorder.Height);
|
||||
}
|
||||
|
||||
clippingGeometry.Rect = new Rect(
|
||||
new Point(leftValue, topValue),
|
||||
new Size(selectBorder.Width - 2, selectBorder.Height - 2));
|
||||
Canvas.SetLeft(selectBorder, leftValue - 1);
|
||||
Canvas.SetTop(selectBorder, topValue - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
isShiftDown = false;
|
||||
|
||||
double left = Math.Min(clickedPoint.X, movingPoint.X);
|
||||
double top = Math.Min(clickedPoint.Y, movingPoint.Y);
|
||||
|
||||
selectBorder.Height = Math.Max(clickedPoint.Y, movingPoint.Y) - top;
|
||||
selectBorder.Width = Math.Max(clickedPoint.X, movingPoint.X) - left;
|
||||
selectBorder.Height += 2;
|
||||
selectBorder.Width += 2;
|
||||
|
||||
clippingGeometry.Rect = new Rect(
|
||||
new Point(left, top),
|
||||
new Size(selectBorder.Width - 2, selectBorder.Height - 2));
|
||||
Canvas.SetLeft(selectBorder, left - 1);
|
||||
Canvas.SetTop(selectBorder, top - 1);
|
||||
}
|
||||
|
||||
private async void RegionClickCanvas_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (IsSelecting == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsSelecting = false;
|
||||
|
||||
CurrentScreen = null;
|
||||
CursorClipper.UnClipCursor();
|
||||
RegionClickCanvas.ReleaseMouseCapture();
|
||||
Matrix m = PresentationSource.FromVisual(this).CompositionTarget.TransformToDevice;
|
||||
|
||||
Point mPt = GetMousePos();
|
||||
Point movingPoint = e.GetPosition(this);
|
||||
movingPoint.X *= m.M11;
|
||||
movingPoint.Y *= m.M22;
|
||||
|
||||
movingPoint.X = Math.Round(movingPoint.X);
|
||||
movingPoint.Y = Math.Round(movingPoint.Y);
|
||||
|
||||
if (mPt == movingPoint)
|
||||
{
|
||||
Debug.WriteLine("Probably on Screen 1");
|
||||
}
|
||||
|
||||
double xDimScaled = Canvas.GetLeft(selectBorder) * m.M11;
|
||||
double yDimScaled = Canvas.GetTop(selectBorder) * m.M22;
|
||||
|
||||
System.Drawing.Rectangle regionScaled = new System.Drawing.Rectangle(
|
||||
(int)xDimScaled,
|
||||
(int)yDimScaled,
|
||||
(int)(selectBorder.Width * m.M11),
|
||||
(int)(selectBorder.Height * m.M22));
|
||||
|
||||
string grabbedText;
|
||||
|
||||
try
|
||||
{
|
||||
RegionClickCanvas.Children.Remove(selectBorder);
|
||||
clippingGeometry.Rect = new Rect(0, 0, 0, 0);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
if (regionScaled.Width < 3 || regionScaled.Height < 3)
|
||||
{
|
||||
BackgroundBrush.Opacity = 0;
|
||||
grabbedText = await ImageMethods.GetClickedWord(this, new Point(xDimScaled, yDimScaled));
|
||||
}
|
||||
else
|
||||
{
|
||||
grabbedText = await ImageMethods.GetRegionsText(this, regionScaled);
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(grabbedText) == false)
|
||||
{
|
||||
Clipboard.SetText(grabbedText);
|
||||
WindowUtilities.CloseAllOCROverlays();
|
||||
}
|
||||
}
|
||||
|
||||
private void CancelMenuItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
WindowUtilities.CloseAllOCROverlays();
|
||||
}
|
||||
}
|
||||
50
src/modules/PowerOCR/PowerOCR/PowerOCR.csproj
Normal file
50
src/modules/PowerOCR/PowerOCR/PowerOCR.csproj
Normal file
@@ -0,0 +1,50 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\Version.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<AssemblyTitle>PowerToys.PowerOCR</AssemblyTitle>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\modules\PowerOCR</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{25C91A4E-BA4E-467A-85CD-8B62545BF674}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<ApplicationIcon>PowerOCRLogo.ico</ApplicationIcon>
|
||||
<PackageIcon>PowerOCRLogo.png</PackageIcon>
|
||||
<RootNamespace>PowerOCR</RootNamespace>
|
||||
<AssemblyName>PowerToys.PowerOCR</AssemblyName>
|
||||
<TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
|
||||
<ProjectTypeGuids>{2150E333-8FDC-42A3-9474-1A3956D46DE8}</ProjectTypeGuids>
|
||||
<IntermediateOutputPath>$(SolutionDir)$(Platform)\$(Configuration)\obj\$(AssemblyName)\</IntermediateOutputPath>
|
||||
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
|
||||
<Optimize>false</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Resource Include="PowerOCRLogo.ico" />
|
||||
<Resource Include="PowerOCRLogo.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.ComponentModel.Composition" Version="6.0.0" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
|
||||
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
BIN
src/modules/PowerOCR/PowerOCR/PowerOCRLogo.ico
Normal file
BIN
src/modules/PowerOCR/PowerOCR/PowerOCRLogo.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 131 KiB |
BIN
src/modules/PowerOCR/PowerOCR/PowerOCRLogo.png
Normal file
BIN
src/modules/PowerOCR/PowerOCR/PowerOCRLogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
12
src/modules/PowerOCR/PowerOCR/Settings/IUserSettings.cs
Normal file
12
src/modules/PowerOCR/PowerOCR/Settings/IUserSettings.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace PowerOCR.Settings;
|
||||
|
||||
public interface IUserSettings
|
||||
{
|
||||
SettingItem<string> ActivationShortcut { get; }
|
||||
|
||||
void SendSettingsTelemetry();
|
||||
}
|
||||
38
src/modules/PowerOCR/PowerOCR/Settings/SettingItem`1.cs
Normal file
38
src/modules/PowerOCR/PowerOCR/Settings/SettingItem`1.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace PowerOCR.Settings;
|
||||
|
||||
public sealed class SettingItem<T> : INotifyPropertyChanged
|
||||
{
|
||||
private T _value;
|
||||
|
||||
public SettingItem(T startValue)
|
||||
{
|
||||
_value = startValue;
|
||||
}
|
||||
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
return _value;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_value = value;
|
||||
OnValueChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
private void OnValueChanged()
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
|
||||
}
|
||||
}
|
||||
120
src/modules/PowerOCR/PowerOCR/Settings/UserSettings.cs
Normal file
120
src/modules/PowerOCR/PowerOCR/Settings/UserSettings.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
|
||||
namespace PowerOCR.Settings
|
||||
{
|
||||
[Export(typeof(IUserSettings))]
|
||||
public class UserSettings : IUserSettings
|
||||
{
|
||||
private readonly ISettingsUtils _settingsUtils;
|
||||
private const string PowerOcrModuleName = "PowerOCR";
|
||||
private const string DefaultActivationShortcut = "Win + Shift + O";
|
||||
private const int MaxNumberOfRetry = 5;
|
||||
private const int SettingsReadOnChangeDelayInMs = 300;
|
||||
|
||||
private readonly IFileSystemWatcher _watcher;
|
||||
private readonly object _loadingSettingsLock = new object();
|
||||
|
||||
[ImportingConstructor]
|
||||
public UserSettings(Helpers.IThrottledActionInvoker throttledActionInvoker)
|
||||
{
|
||||
_settingsUtils = new SettingsUtils();
|
||||
ActivationShortcut = new SettingItem<string>(DefaultActivationShortcut);
|
||||
|
||||
LoadSettingsFromJson();
|
||||
|
||||
// delay loading settings on change by some time to avoid file in use exception
|
||||
_watcher = Helper.GetFileWatcher(PowerOcrModuleName, "settings.json", () => throttledActionInvoker.ScheduleAction(LoadSettingsFromJson, SettingsReadOnChangeDelayInMs));
|
||||
}
|
||||
|
||||
public SettingItem<string> ActivationShortcut { get; private set; }
|
||||
|
||||
private void LoadSettingsFromJson()
|
||||
{
|
||||
// TODO this IO call should by Async, update GetFileWatcher helper to support async
|
||||
lock (_loadingSettingsLock)
|
||||
{
|
||||
{
|
||||
var retry = true;
|
||||
var retryCount = 0;
|
||||
|
||||
while (retry)
|
||||
{
|
||||
try
|
||||
{
|
||||
retryCount++;
|
||||
|
||||
if (!_settingsUtils.SettingsExists(PowerOcrModuleName))
|
||||
{
|
||||
Logger.LogInfo("PowerOCR settings.json was missing, creating a new one");
|
||||
var defaultPowerOcrSettings = new PowerOcrSettings();
|
||||
defaultPowerOcrSettings.Save(_settingsUtils);
|
||||
}
|
||||
|
||||
var settings = _settingsUtils.GetSettingsOrDefault<PowerOcrSettings>(PowerOcrModuleName);
|
||||
if (settings != null)
|
||||
{
|
||||
ActivationShortcut.Value = settings.Properties.ActivationShortcut.ToString();
|
||||
}
|
||||
|
||||
retry = false;
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
if (retryCount > MaxNumberOfRetry)
|
||||
{
|
||||
retry = false;
|
||||
}
|
||||
|
||||
Logger.LogError("Failed to read changed settings", ex);
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (retryCount > MaxNumberOfRetry)
|
||||
{
|
||||
retry = false;
|
||||
}
|
||||
|
||||
Logger.LogError("Failed to read changed settings", ex);
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SendSettingsTelemetry()
|
||||
{
|
||||
Logger.LogInfo("Sending settings telemetry");
|
||||
var settings = _settingsUtils.GetSettingsOrDefault<PowerOcrSettings>(PowerOcrModuleName);
|
||||
var properties = settings?.Properties;
|
||||
if (properties == null)
|
||||
{
|
||||
Logger.LogError("Failed to send settings telemetry");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Send Telemetry when settings change
|
||||
// var telemetrySettings = new Telemetry.PowerOcrSettings(properties.VisibleColorFormats)
|
||||
// {
|
||||
// ActivationShortcut = properties.ActivationShortcut.ToString(),
|
||||
// ActivationBehaviour = properties.ActivationAction.ToString(),
|
||||
// ColorFormatForClipboard = properties.CopiedColorRepresentation.ToString(),
|
||||
// ShowColorName = properties.ShowColorName,
|
||||
// };
|
||||
//
|
||||
// PowerToysTelemetry.Log.WriteEvent(telemetrySettings);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace PowerOCR.Telemetry
|
||||
{
|
||||
[EventData]
|
||||
public class PowerOCRLaunchOverlayEvent : EventBase, IEvent
|
||||
{
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#include <windows.h>
|
||||
#include "resource.h"
|
||||
#include "../../../../common/version/version.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
#include "winres.h"
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION FILE_VERSION
|
||||
PRODUCTVERSION PRODUCT_VERSION
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS VS_FF_DEBUG
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
FILETYPE VFT_DLL
|
||||
FILESUBTYPE VFT2_UNKNOWN
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
|
||||
BEGIN
|
||||
VALUE "CompanyName", COMPANY_NAME
|
||||
VALUE "FileDescription", FILE_DESCRIPTION
|
||||
VALUE "FileVersion", FILE_VERSION_STRING
|
||||
VALUE "InternalName", INTERNAL_NAME
|
||||
VALUE "LegalCopyright", COPYRIGHT_NOTE
|
||||
VALUE "OriginalFilename", ORIGINAL_FILENAME
|
||||
VALUE "ProductName", PRODUCT_NAME
|
||||
VALUE "ProductVersion", PRODUCT_VERSION_STRING
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
|
||||
END
|
||||
END
|
||||
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props')" />
|
||||
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
|
||||
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted $(SolutionDir)tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h PowerOCR.base.rc PowerOCR.rc" />
|
||||
</Target>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}</ProjectGuid>
|
||||
<RootNamespace>PowerOCR</RootNamespace>
|
||||
<ProjectName>PowerOCRModuleInterface</ProjectName>
|
||||
<TargetName>PowerToys.PowerOCRModuleInterface</TargetName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<PlatformToolset>v143</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\modules\PowerOCR\</OutDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="PowerOcrConstants.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<None Include="packages.config" />
|
||||
<None Include="resource.base.h" />
|
||||
<ClInclude Include="trace.h" />
|
||||
<ClInclude Include="Generated Files\resource.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(CIBuild)'!='true'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="trace.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
|
||||
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
|
||||
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="PowerOCR.base.rc" />
|
||||
<ResourceCompile Include="Generated Files\PowerOCR.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Resources.resx">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<Import Project="..\..\..\..\deps\spdlog.props" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets')" />
|
||||
</ImportGroup>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.220418.1\build\native\Microsoft.Windows.CppWinRT.targets'))" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Generated Files">
|
||||
<UniqueIdentifier>{875a08c6-f610-4667-bd0f-80171ed96072}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="pch.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Generated Files\resource.h">
|
||||
<Filter>Generated Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="PowerOcrConstants.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="trace.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="pch.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="trace.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="resource.base.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</None>
|
||||
<None Include="Resources.resx">
|
||||
<Filter>Resource Files</Filter>
|
||||
</None>
|
||||
<None Include="packages.config" />
|
||||
<None Include="PowerOCR.base.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="Generated Files\PowerOCR.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
namespace PowerOcrConstants
|
||||
{
|
||||
// Name of the powertoy module.
|
||||
inline const std::wstring ModuleKey = L"PowerOCR";
|
||||
}
|
||||
126
src/modules/PowerOCR/PowerOCRModuleInterface/Resources.resx
Normal file
126
src/modules/PowerOCR/PowerOCRModuleInterface/Resources.resx
Normal file
@@ -0,0 +1,126 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="PowerOCR_Name" xml:space="preserve">
|
||||
<value>PowerOCR</value>
|
||||
</data>
|
||||
<data name="PowerOCR_Settings_Desc" xml:space="preserve">
|
||||
<value>This feature requires Windows 10 version 1903 or higher</value>
|
||||
</data>
|
||||
</root>
|
||||
294
src/modules/PowerOCR/PowerOCRModuleInterface/dllmain.cpp
Normal file
294
src/modules/PowerOCR/PowerOCRModuleInterface/dllmain.cpp
Normal file
@@ -0,0 +1,294 @@
|
||||
// dllmain.cpp : Defines the entry point for the DLL application.
|
||||
#include "pch.h"
|
||||
|
||||
#include <interface/powertoy_module_interface.h>
|
||||
#include "trace.h"
|
||||
#include "Generated Files/resource.h"
|
||||
#include <common/logger/logger.h>
|
||||
#include <common/SettingsAPI/settings_objects.h>
|
||||
#include <common/utils/resources.h>
|
||||
|
||||
#include <PowerOCR/PowerOCRModuleInterface/PowerOcrConstants.h>
|
||||
#include <common/interop/shared_constants.h>
|
||||
#include <common/utils/logger_helper.h>
|
||||
#include <common/utils/winapi_error.h>
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE hModule,
|
||||
DWORD ul_reason_for_call,
|
||||
LPVOID lpReserved)
|
||||
{
|
||||
switch (ul_reason_for_call)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
Trace::RegisterProvider();
|
||||
case DLL_THREAD_ATTACH:
|
||||
break;
|
||||
case DLL_THREAD_DETACH:
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
Trace::UnregisterProvider();
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
const wchar_t JSON_KEY_PROPERTIES[] = L"properties";
|
||||
const wchar_t JSON_KEY_WIN[] = L"win";
|
||||
const wchar_t JSON_KEY_ALT[] = L"alt";
|
||||
const wchar_t JSON_KEY_CTRL[] = L"ctrl";
|
||||
const wchar_t JSON_KEY_SHIFT[] = L"shift";
|
||||
const wchar_t JSON_KEY_CODE[] = L"code";
|
||||
const wchar_t JSON_KEY_ACTIVATION_SHORTCUT[] = L"ActivationShortcut";
|
||||
}
|
||||
|
||||
struct ModuleSettings
|
||||
{
|
||||
} g_settings;
|
||||
|
||||
class PowerOCR : public PowertoyModuleIface
|
||||
{
|
||||
private:
|
||||
bool m_enabled = false;
|
||||
|
||||
std::wstring app_name;
|
||||
|
||||
//contains the non localized key of the powertoy
|
||||
std::wstring app_key;
|
||||
|
||||
HANDLE m_hProcess;
|
||||
|
||||
// Time to wait for process to close after sending WM_CLOSE signal
|
||||
static const int MAX_WAIT_MILLISEC = 10000;
|
||||
|
||||
Hotkey m_hotkey;
|
||||
|
||||
// Handle to event used to invoke PowerOCR
|
||||
HANDLE m_hInvokeEvent;
|
||||
|
||||
void parse_hotkey(PowerToysSettings::PowerToyValues& settings)
|
||||
{
|
||||
auto settingsObject = settings.get_raw_json();
|
||||
if (settingsObject.GetView().Size())
|
||||
{
|
||||
try
|
||||
{
|
||||
auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(JSON_KEY_ACTIVATION_SHORTCUT);
|
||||
m_hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
|
||||
m_hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
|
||||
m_hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
|
||||
m_hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
|
||||
m_hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Logger::error("Failed to initialize PowerOCR start shortcut");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::info("PowerOCR settings are empty");
|
||||
}
|
||||
|
||||
if (!m_hotkey.key)
|
||||
{
|
||||
Logger::info("PowerOCR is going to use default shortcut");
|
||||
m_hotkey.win = true;
|
||||
m_hotkey.alt = false;
|
||||
m_hotkey.shift = true;
|
||||
m_hotkey.ctrl = false;
|
||||
m_hotkey.key = 'T';
|
||||
}
|
||||
}
|
||||
|
||||
bool is_process_running()
|
||||
{
|
||||
return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
|
||||
}
|
||||
|
||||
void launch_process()
|
||||
{
|
||||
Logger::trace(L"Starting PowerOCR process");
|
||||
unsigned long powertoys_pid = GetCurrentProcessId();
|
||||
|
||||
std::wstring executable_args = L"";
|
||||
executable_args.append(std::to_wstring(powertoys_pid));
|
||||
|
||||
SHELLEXECUTEINFOW sei{ sizeof(sei) };
|
||||
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
|
||||
sei.lpFile = L"modules\\PowerOCR\\PowerToys.PowerOCR.exe";
|
||||
sei.nShow = SW_SHOWNORMAL;
|
||||
sei.lpParameters = executable_args.data();
|
||||
if (ShellExecuteExW(&sei))
|
||||
{
|
||||
Logger::trace("Successfully started the PowerOCR process");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::error( L"PowerOCR failed to start. {}", get_last_error_or_default(GetLastError()));
|
||||
}
|
||||
|
||||
m_hProcess = sei.hProcess;
|
||||
}
|
||||
|
||||
// Load the settings file.
|
||||
void init_settings()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Load and parse the settings file for this PowerToy.
|
||||
PowerToysSettings::PowerToyValues settings =
|
||||
PowerToysSettings::PowerToyValues::load_from_settings_file(get_key());
|
||||
|
||||
parse_hotkey(settings);
|
||||
}
|
||||
catch (std::exception ex)
|
||||
{
|
||||
Logger::warn(L"An exception occurred while loading the settings file");
|
||||
// Error while loading from the settings file. Let default values stay as they are.
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
PowerOCR()
|
||||
{
|
||||
app_name = GET_RESOURCE_STRING(IDS_POWEROCR_NAME);
|
||||
app_key = PowerOcrConstants::ModuleKey;
|
||||
LoggerHelpers::init_logger(app_key, L"ModuleInterface", "PowerOCR");
|
||||
m_hInvokeEvent = CreateDefaultEvent(CommonSharedConstants::SHOW_POWEROCR_SHARED_EVENT);
|
||||
init_settings();
|
||||
}
|
||||
|
||||
~PowerOCR()
|
||||
{
|
||||
if (m_enabled)
|
||||
{
|
||||
}
|
||||
m_enabled = false;
|
||||
}
|
||||
|
||||
// Destroy the powertoy and free memory
|
||||
virtual void destroy() override
|
||||
{
|
||||
Logger::trace("PowerOCR::destroy()");
|
||||
delete this;
|
||||
}
|
||||
|
||||
// Return the localized display name of the powertoy
|
||||
virtual const wchar_t* get_name() override
|
||||
{
|
||||
return app_name.c_str();
|
||||
}
|
||||
|
||||
// Return the non localized key of the powertoy, this will be cached by the runner
|
||||
virtual const wchar_t* get_key() override
|
||||
{
|
||||
return app_key.c_str();
|
||||
}
|
||||
|
||||
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
|
||||
{
|
||||
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
|
||||
|
||||
// Create a Settings object.
|
||||
PowerToysSettings::Settings settings(hinstance, get_name());
|
||||
settings.set_description(GET_RESOURCE_STRING(IDS_POWEROCR_SETTINGS_DESC));
|
||||
|
||||
settings.set_overview_link(L"https://aka.ms/PowerToysOverview_PowerOCR");
|
||||
|
||||
return settings.serialize_to_buffer(buffer, buffer_size);
|
||||
}
|
||||
|
||||
virtual void call_custom_action(const wchar_t* action) override
|
||||
{
|
||||
}
|
||||
|
||||
virtual void set_config(const wchar_t* config) override
|
||||
{
|
||||
try
|
||||
{
|
||||
// Parse the input JSON string.
|
||||
PowerToysSettings::PowerToyValues values =
|
||||
PowerToysSettings::PowerToyValues::from_json_string(config, get_key());
|
||||
|
||||
parse_hotkey(values);
|
||||
// If you don't need to do any custom processing of the settings, proceed
|
||||
// to persists the values calling:
|
||||
values.save_to_settings_file();
|
||||
// Otherwise call a custom function to process the settings before saving them to disk:
|
||||
// save_settings();
|
||||
}
|
||||
catch (std::exception ex)
|
||||
{
|
||||
// Improper JSON.
|
||||
}
|
||||
}
|
||||
|
||||
virtual void enable()
|
||||
{
|
||||
Logger::trace("PowerOCR::enable()");
|
||||
ResetEvent(m_hInvokeEvent);
|
||||
launch_process();
|
||||
m_enabled = true;
|
||||
Trace::EnablePowerOCR(true);
|
||||
};
|
||||
|
||||
virtual void disable()
|
||||
{
|
||||
Logger::trace("PowerOCR::disable()");
|
||||
if (m_enabled)
|
||||
{
|
||||
ResetEvent(m_hInvokeEvent);
|
||||
TerminateProcess(m_hProcess, 1);
|
||||
}
|
||||
|
||||
m_enabled = false;
|
||||
Trace::EnablePowerOCR(false);
|
||||
}
|
||||
|
||||
virtual bool on_hotkey(size_t hotkeyId) override
|
||||
{
|
||||
if (m_enabled)
|
||||
{
|
||||
Logger::trace(L"PowerOCR hotkey pressed");
|
||||
if (!is_process_running())
|
||||
{
|
||||
launch_process();
|
||||
}
|
||||
|
||||
SetEvent(m_hInvokeEvent);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
|
||||
{
|
||||
if (m_hotkey.key)
|
||||
{
|
||||
if (hotkeys && buffer_size >= 1)
|
||||
{
|
||||
hotkeys[0] = m_hotkey;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
virtual bool is_enabled() override
|
||||
{
|
||||
return m_enabled;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
|
||||
{
|
||||
return new PowerOCR();
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Microsoft.Windows.CppWinRT" version="2.0.220418.1" targetFramework="native" />
|
||||
</packages>
|
||||
2
src/modules/PowerOCR/PowerOCRModuleInterface/pch.cpp
Normal file
2
src/modules/PowerOCR/PowerOCRModuleInterface/pch.cpp
Normal file
@@ -0,0 +1,2 @@
|
||||
#include "pch.h"
|
||||
|
||||
7
src/modules/PowerOCR/PowerOCRModuleInterface/pch.h
Normal file
7
src/modules/PowerOCR/PowerOCRModuleInterface/pch.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <ProjectTelemetry.h>
|
||||
#include <shellapi.h>
|
||||
#include <Shlwapi.h>
|
||||
13
src/modules/PowerOCR/PowerOCRModuleInterface/resource.base.h
Normal file
13
src/modules/PowerOCR/PowerOCRModuleInterface/resource.base.h
Normal file
@@ -0,0 +1,13 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by PowerOCR.rc
|
||||
|
||||
//////////////////////////////
|
||||
// Non-localizable
|
||||
|
||||
#define FILE_DESCRIPTION "PowerToys PowerOCR"
|
||||
#define INTERNAL_NAME "PowerToys.PowerOCR"
|
||||
#define ORIGINAL_FILENAME "PowerToys.PowerOCR.dll"
|
||||
|
||||
// Non-localizable
|
||||
//////////////////////////////
|
||||
30
src/modules/PowerOCR/PowerOCRModuleInterface/trace.cpp
Normal file
30
src/modules/PowerOCR/PowerOCRModuleInterface/trace.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "pch.h"
|
||||
#include "trace.h"
|
||||
|
||||
TRACELOGGING_DEFINE_PROVIDER(
|
||||
g_hProvider,
|
||||
"Microsoft.PowerToys",
|
||||
// {38e8889b-9731-53f5-e901-e8a7c1753074}
|
||||
(0x38e8889b, 0x9731, 0x53f5, 0xe9, 0x01, 0xe8, 0xa7, 0xc1, 0x75, 0x30, 0x74),
|
||||
TraceLoggingOptionProjectTelemetry());
|
||||
|
||||
void Trace::RegisterProvider()
|
||||
{
|
||||
TraceLoggingRegister(g_hProvider);
|
||||
}
|
||||
|
||||
void Trace::UnregisterProvider()
|
||||
{
|
||||
TraceLoggingUnregister(g_hProvider);
|
||||
}
|
||||
|
||||
// Log if the user has PowerOCR enabled or disabled
|
||||
void Trace::EnablePowerOCR(const bool enabled) noexcept
|
||||
{
|
||||
TraceLoggingWrite(
|
||||
g_hProvider,
|
||||
"PowerOCR_EnablePowerOCR",
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
|
||||
TraceLoggingBoolean(enabled, "Enabled"));
|
||||
}
|
||||
10
src/modules/PowerOCR/PowerOCRModuleInterface/trace.h
Normal file
10
src/modules/PowerOCR/PowerOCRModuleInterface/trace.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
class Trace
|
||||
{
|
||||
public:
|
||||
static void RegisterProvider();
|
||||
static void UnregisterProvider();
|
||||
|
||||
// Log if the user has PowerOCR enabled or disabled
|
||||
static void EnablePowerOCR(const bool enabled) noexcept;
|
||||
};
|
||||
Reference in New Issue
Block a user