mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-15 19:27:56 +01: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:
3
.github/actions/spell-check/expect.txt
vendored
3
.github/actions/spell-check/expect.txt
vendored
@@ -1415,6 +1415,7 @@ Objbase
|
||||
OBJID
|
||||
objidl
|
||||
oblitum
|
||||
ocr
|
||||
odbc
|
||||
odbccp
|
||||
Oem
|
||||
@@ -1474,6 +1475,7 @@ PARENTNOTIFY
|
||||
PARENTRELATIVEEDITING
|
||||
PARENTRELATIVEFORADDRESSBAR
|
||||
PARENTRELATIVEPARSING
|
||||
PArgb
|
||||
parray
|
||||
PARTIALCONFIRMATIONDIALOGTITLE
|
||||
pathcch
|
||||
@@ -1530,6 +1532,7 @@ POPUPWINDOW
|
||||
posix
|
||||
powercfg
|
||||
powerlauncher
|
||||
POWEROCR
|
||||
powerpreview
|
||||
powerrename
|
||||
POWERRENAMECONTEXTMENU
|
||||
|
||||
@@ -25,14 +25,14 @@
|
||||
"modules\\AlwaysOnTop\\PowerToys.AlwaysOnTop.exe",
|
||||
"modules\\AlwaysOnTop\\PowerToys.AlwaysOnTopModuleInterface.dll",
|
||||
|
||||
"modules\\ColorPicker\\ColorPicker.dll",
|
||||
"modules\\ColorPicker\\ColorPickerUI.dll",
|
||||
"modules\\ColorPicker\\ColorPickerUI.exe",
|
||||
|
||||
"modules\\ColorPicker\\PowerToys.ColorPicker.dll",
|
||||
"modules\\ColorPicker\\PowerToys.ColorPickerUI.dll",
|
||||
"modules\\ColorPicker\\PowerToys.ColorPickerUI.exe",
|
||||
|
||||
"modules\\PowerOCR\\PowerToys.PowerOCRModuleInterface.dll",
|
||||
"modules\\PowerOCR\\PowerToys.PowerOCR.dll",
|
||||
"modules\\PowerOCR\\PowerToys.PowerOCR.exe",
|
||||
|
||||
"modules\\Awake\\PowerToys.AwakeModuleInterface.dll",
|
||||
"modules\\Awake\\PowerToys.Awake.exe",
|
||||
"modules\\Awake\\PowerToys.Awake.dll",
|
||||
|
||||
@@ -415,6 +415,12 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImageResizerContextMenu", "
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ImageResizerLib", "src\modules\imageresizer\ImageResizerLib\ImageResizerLib.vcxproj", "{18B3DB45-4FFE-4D01-97D6-5223FEEE1853}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PowerOCR", "PowerOCR", "{A50C70A6-2DA0-4027-B90E-B1A40755A8A5}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerOCR", "src\modules\PowerOCR\PowerOCR\PowerOCR.csproj", "{25C91A4E-BA4E-467A-85CD-8B62545BF674}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PowerOCRModuleInterface", "src\modules\PowerOCR\PowerOCRModuleInterface\PowerOCRModuleInterface.vcxproj", "{6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.History", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.History\Microsoft.PowerToys.Run.Plugin.History.csproj", "{212AD910-8488-4036-BE20-326931B75FB2}"
|
||||
EndProject
|
||||
Global
|
||||
@@ -1634,6 +1640,30 @@ Global
|
||||
{18B3DB45-4FFE-4D01-97D6-5223FEEE1853}.Release|x64.Build.0 = Release|x64
|
||||
{18B3DB45-4FFE-4D01-97D6-5223FEEE1853}.Release|x86.ActiveCfg = Release|x64
|
||||
{18B3DB45-4FFE-4D01-97D6-5223FEEE1853}.Release|x86.Build.0 = Release|x64
|
||||
{25C91A4E-BA4E-467A-85CD-8B62545BF674}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{25C91A4E-BA4E-467A-85CD-8B62545BF674}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{25C91A4E-BA4E-467A-85CD-8B62545BF674}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{25C91A4E-BA4E-467A-85CD-8B62545BF674}.Debug|x64.Build.0 = Debug|x64
|
||||
{25C91A4E-BA4E-467A-85CD-8B62545BF674}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{25C91A4E-BA4E-467A-85CD-8B62545BF674}.Debug|x86.Build.0 = Debug|x64
|
||||
{25C91A4E-BA4E-467A-85CD-8B62545BF674}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{25C91A4E-BA4E-467A-85CD-8B62545BF674}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{25C91A4E-BA4E-467A-85CD-8B62545BF674}.Release|x64.ActiveCfg = Release|x64
|
||||
{25C91A4E-BA4E-467A-85CD-8B62545BF674}.Release|x64.Build.0 = Release|x64
|
||||
{25C91A4E-BA4E-467A-85CD-8B62545BF674}.Release|x86.ActiveCfg = Release|x64
|
||||
{25C91A4E-BA4E-467A-85CD-8B62545BF674}.Release|x86.Build.0 = Release|x64
|
||||
{6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Debug|x64.Build.0 = Debug|x64
|
||||
{6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Debug|x86.Build.0 = Debug|x64
|
||||
{6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Release|x64.ActiveCfg = Release|x64
|
||||
{6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Release|x64.Build.0 = Release|x64
|
||||
{6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Release|x86.ActiveCfg = Release|x64
|
||||
{6AB6A2D6-F859-4A82-9184-0BD29C9F07D1}.Release|x86.Build.0 = Release|x64
|
||||
{212AD910-8488-4036-BE20-326931B75FB2}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{212AD910-8488-4036-BE20-326931B75FB2}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{212AD910-8488-4036-BE20-326931B75FB2}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -1784,6 +1814,9 @@ Global
|
||||
{5A1DB2F0-0715-4B3B-98E6-79BC41540045} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
|
||||
{93B72A06-C8BD-484F-A6F7-C9F280B150BF} = {6C7F47CC-2151-44A3-A546-41C70025132C}
|
||||
{18B3DB45-4FFE-4D01-97D6-5223FEEE1853} = {6C7F47CC-2151-44A3-A546-41C70025132C}
|
||||
{A50C70A6-2DA0-4027-B90E-B1A40755A8A5} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC}
|
||||
{25C91A4E-BA4E-467A-85CD-8B62545BF674} = {A50C70A6-2DA0-4027-B90E-B1A40755A8A5}
|
||||
{6AB6A2D6-F859-4A82-9184-0BD29C9F07D1} = {A50C70A6-2DA0-4027-B90E-B1A40755A8A5}
|
||||
{212AD910-8488-4036-BE20-326931B75FB2} = {4AFC9975-2456-4C70-94A4-84073C1CED93}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<?define KeyboardManagerProjectName="KeyboardManager"?>
|
||||
<?define PowerRenameProjectName="PowerRename"?>
|
||||
<?define ColorPickerProjectName="ColorPicker"?>
|
||||
<?define PowerOCRProjectName="PowerOCR"?>
|
||||
<?define VideoConferenceProjectName="VideoConference"?>
|
||||
<?define AwakeProjectName="Awake"?>
|
||||
<?define MouseUtilsProjectName="MouseUtils"?>
|
||||
@@ -48,11 +49,11 @@
|
||||
|
||||
<?define SettingsV2Files=Ijwhost.dll;ColorCode.Core.dll;ColorCode.WinUI.dll;CommunityToolkit.Common.dll;CommunityToolkit.WinUI.dll;CommunityToolkit.WinUI.UI.Controls.Core.dll;CommunityToolkit.WinUI.UI.Controls.DataGrid.dll;CommunityToolkit.WinUI.UI.Controls.Input.dll;CommunityToolkit.WinUI.UI.Controls.Layout.dll;CommunityToolkit.WinUI.UI.Controls.Markdown.dll;CommunityToolkit.WinUI.UI.Controls.Media.dll;CommunityToolkit.WinUI.UI.Controls.Primitives.dll;CommunityToolkit.WinUI.UI.dll;icon.ico;Microsoft.Graphics.Canvas.Interop.dll;Microsoft.InteractiveExperiences.Projection.dll;Microsoft.Windows.ApplicationModel.DynamicDependency.Projection.dll;Microsoft.Windows.ApplicationModel.Resources.Projection.dll;Microsoft.Windows.ApplicationModel.WindowsAppRuntime.Projection.dll;Microsoft.Windows.AppLifecycle.Projection.dll;Microsoft.Windows.SDK.NET.dll;Microsoft.Windows.System.Power.Projection.dll;Microsoft.WindowsAppRuntime.Bootstrap.Net.dll;Microsoft.WinUI.dll;Microsoft.Xaml.Interactions.dll;Microsoft.Xaml.Interactivity.dll;PowerToys.Interop.dll;PowerToys.ManagedCommon.dll;System.Management.dll;PowerToys.ManagedTelemetry.dll;PowerToys.Settings.deps.json;PowerToys.Settings.dll;PowerToys.Settings.exe;PowerToys.Settings.runtimeconfig.json;PowerToys.Settings.UI.Lib.dll;resources.pri;System.IO.Abstractions.dll;System.Text.Json.dll;WinRT.Runtime.dll;Microsoft.Graphics.Canvas.dll;Microsoft.WindowsAppRuntime.Bootstrap.dll;CoreMessagingXP.dll;dcompi.dll;dwmcorei.dll;DwmSceneI.dll;DWriteCore.dll;marshal.dll;Microsoft.DirectManipulation.dll;Microsoft.InputStateManager.dll;Microsoft.Internal.FrameworkUdk.dll;Microsoft.UI.Composition.OSSupport.dll;Microsoft.UI.Input.dll;Microsoft.UI.Windowing.Core.dll;Microsoft.UI.Xaml.Controls.dll;Microsoft.UI.Xaml.Controls.pri;Microsoft.ui.xaml.dll;Microsoft.UI.Xaml.Internal.dll;Microsoft.UI.Xaml.Phone.dll;Microsoft.ui.xaml.resources.19h1.dll;Microsoft.ui.xaml.resources.common.dll;Microsoft.Web.WebView2.Core.dll;Microsoft.Windows.ApplicationModel.Resources.dll;Microsoft.Windows.AppNotifications.Projection.dll;Microsoft.Windows.PushNotifications.Projection.dll;Microsoft.Windows.System.Projection.dll;Microsoft.WindowsAppRuntime.dll;Microsoft.WindowsAppRuntime.Insights.Resource.dll;Microsoft.WindowsAppRuntime.Release.Net.dll;MRM.dll;PushNotificationsLongRunningTask.ProxyStub.dll;WindowsAppRuntime.png;WindowsAppSdk.AppxDeploymentExtensions.Desktop.dll;WinUIEdit.dll;wuceffectsi.dll?>
|
||||
|
||||
<?define SettingsV2AssetsModulesFiles=ColorPicker.png;FancyZones.png;AlwaysOnTop.png;Awake.png;ImageResizer.png;KBM.png;MouseUtils.png;PowerLauncher.png;PowerPreview.png;PowerRename.png;PT.png;ShortcutGuide.png;VideoConference.png?>
|
||||
<?define SettingsV2AssetsModulesFiles=ColorPicker.png;FancyZones.png;AlwaysOnTop.png;Awake.png;ImageResizer.png;KBM.png;MouseUtils.png;PowerLauncher.png;PowerPreview.png;PowerRename.png;PT.png;ShortcutGuide.png;VideoConference.png;PowerOCR.png?>
|
||||
|
||||
<?define SettingsV2OOBEAssetsModulesFiles=ColorPicker.gif;AlwaysOnTop.png;Awake.png;FancyZones.gif;FileExplorer.png;ImageResizer.gif;KBM.gif;MouseUtils.gif;PowerRename.gif;Run.gif;OOBEShortcutGuide.png;VideoConferenceMute.png;OOBEPTHero.png?>
|
||||
<?define SettingsV2OOBEAssetsModulesFiles=ColorPicker.gif;AlwaysOnTop.png;Awake.png;FancyZones.gif;FileExplorer.png;ImageResizer.gif;KBM.gif;MouseUtils.gif;PowerRename.gif;Run.gif;OOBEShortcutGuide.png;VideoConferenceMute.png;OOBEPTHero.png;PowerOCR.gif?>
|
||||
|
||||
<?define SettingsV2OOBEAssetsFluentIconsFiles=ColorPicker.png;FancyZones.png;AlwaysOnTop.png;Awake.png;FileExplorerPreview.png;FindMyMouse.png;ImageResizer.png;KeyboardManager.png;MouseHighlighter.png;MouseCrosshairs.png;MouseUtils.png;PowerRename.png;PowerToys.png;PowerToysRun.png;Settings.png;ShortcutGuide.png;VideoConferenceMute.png?>
|
||||
<?define SettingsV2OOBEAssetsFluentIconsFiles=ColorPicker.png;FancyZones.png;AlwaysOnTop.png;Awake.png;FileExplorerPreview.png;FindMyMouse.png;ImageResizer.png;KeyboardManager.png;MouseHighlighter.png;MouseCrosshairs.png;MouseUtils.png;PowerRename.png;PowerToys.png;PowerToysRun.png;Settings.png;ShortcutGuide.png;VideoConferenceMute.png;PowerOcr.png?>
|
||||
|
||||
<?define SettingsV2MicrosoftUIXamlAssetsInstallFiles=NoiseAsset_256x256_PNG.png?>
|
||||
|
||||
@@ -431,6 +432,7 @@
|
||||
<Directory Id="MonacoPreviewHandlerMonacoSRCFolder" Name="monacoSRC" />
|
||||
</Directory>
|
||||
<Directory Id="FancyZonesInstallFolder" Name="$(var.FancyZonesProjectName)" />
|
||||
<Directory Id="PowerOCRInstallFolder" Name="$(var.PowerOCRProjectName)" />
|
||||
<Directory Id="AwakeInstallFolder" Name="$(var.AwakeProjectName)">
|
||||
<Directory Id="AwakeImagesFolder" Name="Images" />
|
||||
</Directory>
|
||||
@@ -894,6 +896,16 @@
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<!-- PowerOCR Resources -->
|
||||
<DirectoryRef Id="PowerOCRInstallFolder" FileSource="$(var.BinDir)modules\$(var.PowerOCRProjectName)">
|
||||
<!-- !Warning! Make sure to change Component Guid if you update the file list -->
|
||||
<Component Id="Module_PowerOCR" Guid="5640A7E8-E165-4368-8F08-F8E1E9242BDD" Win64="yes">
|
||||
<?foreach File in PowerToys.PowerOCR.dll;ControlzEx.dll;Ijwhost.dll;Microsoft.Windows.SDK.NET.dll;Microsoft.Xaml.Behaviors.dll;PowerToys.Common.UI.dll;PowerToys.Interop.dll;PowerToys.ManagedCommon.dll;PowerToys.ManagedTelemetry.dll;PowerToys.PowerOCR.deps.json;PowerToys.PowerOCR.exe;PowerToys.PowerOCR.runtimeconfig.json;PowerToys.PowerOCRModuleInterface.dll;PowerToys.Settings.UI.Lib.dll;System.ComponentModel.Composition.dll;System.IO.Abstractions.dll;System.Management.dll;System.Text.Json.dll;WinRT.Runtime.dll?>
|
||||
<File Id="PowerOCRFile_$(var.File)" Source="$(var.BinDir)modules\$(var.PowerOCRProjectName)\$(var.File)" />
|
||||
<?endforeach?>
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<!-- Awake -->
|
||||
<DirectoryRef Id="AwakeInstallFolder" FileSource="$(var.BinDir)modules\$(var.AwakeProjectName)">
|
||||
<!-- !Warning! Make sure to change Component Guid if you update the file list -->
|
||||
@@ -1063,6 +1075,7 @@
|
||||
<ComponentRef Id="Module_KeyboardManager" />
|
||||
<ComponentRef Id="Module_KeyboardManager_Editor" />
|
||||
<ComponentRef Id="Module_KeyboardManager_Engine" />
|
||||
<ComponentRef Id="Module_PowerOCR" />
|
||||
<ComponentRef Id="Module_ColorPicker" />
|
||||
<ComponentRef Id="Module_ColorPicker_Icon"/>
|
||||
<ComponentRef Id="Module_ColorPicker_Cursor"/>
|
||||
|
||||
@@ -195,8 +195,13 @@ public
|
||||
return gcnew String(CommonSharedConstants::SHOW_COLOR_PICKER_SHARED_EVENT);
|
||||
}
|
||||
|
||||
static String ^ ShowPowerOCRSharedEvent() {
|
||||
return gcnew String(CommonSharedConstants::SHOW_POWEROCR_SHARED_EVENT);
|
||||
}
|
||||
|
||||
static String ^ AwakeExitEvent() {
|
||||
return gcnew String(CommonSharedConstants::AWAKE_EXIT_EVENT);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@ namespace CommonSharedConstants
|
||||
// Path to the event used by AlwaysOnTop
|
||||
const wchar_t ALWAYS_ON_TOP_PIN_EVENT[] = L"Local\\AlwaysOnTopPinEvent-892e0aa2-cfa8-4cc4-b196-ddeb32314ce8";
|
||||
|
||||
// Path to the event used by PowerOCR
|
||||
const wchar_t SHOW_POWEROCR_SHARED_EVENT[] = L"Local\\PowerOCREvent-dc864e06-e1af-4ecc-9078-f98bee745e3a";
|
||||
|
||||
// Max DWORD for key code to disable keys.
|
||||
const DWORD VK_DISABLED = 0x100;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ struct LogSettings
|
||||
inline const static std::wstring fancyZonesOldLogPath = L"FancyZonesLogs\\"; // needed to clean up old logs
|
||||
inline const static std::string shortcutGuideLoggerName = "shortcut-guide";
|
||||
inline const static std::wstring shortcutGuideLogPath = L"ShortcutGuideLogs\\shortcut-guide-log.txt";
|
||||
inline const static std::wstring powerOcrLogPath = L"Logs\\power-ocr-log.txt";
|
||||
inline const static std::string keyboardManagerLoggerName = "keyboard-manager";
|
||||
inline const static std::wstring keyboardManagerLogPath = L"Logs\\keyboard-manager-log.txt";
|
||||
inline const static std::string findMyMouseLoggerName = "find-my-mouse";
|
||||
@@ -31,6 +32,7 @@ struct LogSettings
|
||||
inline const static std::string imageResizerLoggerName = "imageresizer";
|
||||
inline const static std::string powerRenameLoggerName = "powerrename";
|
||||
inline const static std::string alwaysOnTopLoggerName = "always-on-top";
|
||||
inline const static std::string powerOcrLoggerName = "power-ocr";
|
||||
inline const static std::wstring alwaysOnTopLogPath = L"always-on-top-log.txt";
|
||||
inline const static int retention = 30;
|
||||
std::wstring logLevel;
|
||||
|
||||
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;
|
||||
};
|
||||
@@ -153,6 +153,8 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow
|
||||
L"modules/MouseUtils/PowerToys.MouseHighlighter.dll",
|
||||
L"modules/AlwaysOnTop/PowerToys.AlwaysOnTopModuleInterface.dll",
|
||||
L"modules/MouseUtils/PowerToys.MousePointerCrosshairs.dll",
|
||||
L"modules/PowerOCR/PowerToys.PowerOCRModuleInterface.dll",
|
||||
|
||||
};
|
||||
const auto VCM_PATH = L"modules/VideoConference/PowerToys.VideoConferenceModule.dll";
|
||||
if (const auto mf = LoadLibraryA("mf.dll"))
|
||||
|
||||
@@ -239,6 +239,22 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
}
|
||||
}
|
||||
|
||||
private bool powerOCR = true;
|
||||
|
||||
[JsonPropertyName("PowerOCR")]
|
||||
public bool PowerOCR
|
||||
{
|
||||
get => powerOCR;
|
||||
set
|
||||
{
|
||||
if (powerOCR != value)
|
||||
{
|
||||
LogTelemetryEvent(value);
|
||||
powerOCR = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string ToJsonString()
|
||||
{
|
||||
return JsonSerializer.Serialize(this);
|
||||
|
||||
21
src/settings-ui/Settings.UI.Library/PowerOcrProperties.cs
Normal file
21
src/settings-ui/Settings.UI.Library/PowerOcrProperties.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
// 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.Text.Json;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public class PowerOcrProperties
|
||||
{
|
||||
public PowerOcrProperties()
|
||||
{
|
||||
ActivationShortcut = new HotkeySettings(true, false, false, true, 0x54); // Win+Shift+T
|
||||
}
|
||||
|
||||
public HotkeySettings ActivationShortcut { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
=> JsonSerializer.Serialize(this);
|
||||
}
|
||||
}
|
||||
49
src/settings-ui/Settings.UI.Library/PowerOcrSettings.cs
Normal file
49
src/settings-ui/Settings.UI.Library/PowerOcrSettings.cs
Normal file
@@ -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.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public class PowerOcrSettings : BasePTModuleSettings, ISettingsConfig
|
||||
{
|
||||
public const string ModuleName = "PowerOCR";
|
||||
|
||||
[JsonPropertyName("properties")]
|
||||
public PowerOcrProperties Properties { get; set; }
|
||||
|
||||
public PowerOcrSettings()
|
||||
{
|
||||
Properties = new PowerOcrProperties();
|
||||
Version = "1";
|
||||
Name = ModuleName;
|
||||
}
|
||||
|
||||
public virtual void Save(ISettingsUtils settingsUtils)
|
||||
{
|
||||
// Save settings to file
|
||||
var options = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
};
|
||||
|
||||
if (settingsUtils == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(settingsUtils));
|
||||
}
|
||||
|
||||
settingsUtils.SaveSettings(JsonSerializer.Serialize(this, options), ModuleName);
|
||||
}
|
||||
|
||||
public string GetModuleName()
|
||||
=> Name;
|
||||
|
||||
// This can be utilized in the future if the settings.json file is to be modified/deleted.
|
||||
public bool UpgradeSettingsConfiguration()
|
||||
=> false;
|
||||
}
|
||||
}
|
||||
29
src/settings-ui/Settings.UI.Library/SndPowerOcrSettings.cs
Normal file
29
src/settings-ui/Settings.UI.Library/SndPowerOcrSettings.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.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
{
|
||||
public class SndPowerOcrSettings
|
||||
{
|
||||
[JsonPropertyName("PowerOCR")]
|
||||
public PowerOcrSettings Settings { get; set; }
|
||||
|
||||
public SndPowerOcrSettings()
|
||||
{
|
||||
}
|
||||
|
||||
public SndPowerOcrSettings(PowerOcrSettings settings)
|
||||
{
|
||||
Settings = settings;
|
||||
}
|
||||
|
||||
public string ToJsonString()
|
||||
{
|
||||
return JsonSerializer.Serialize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// 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.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Timers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Enumerations;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Library.ViewModels
|
||||
{
|
||||
public class PowerOcrViewModel : Observable, IDisposable
|
||||
{
|
||||
private bool disposedValue;
|
||||
|
||||
// Delay saving of settings in order to avoid calling save multiple times and hitting file in use exception. If there is no other request to save settings in given interval, we proceed to save it, otherwise we schedule saving it after this interval
|
||||
private const int SaveSettingsDelayInMs = 500;
|
||||
|
||||
private GeneralSettings GeneralSettingsConfig { get; set; }
|
||||
|
||||
private readonly ISettingsUtils _settingsUtils;
|
||||
private readonly object _delayedActionLock = new object();
|
||||
|
||||
private readonly PowerOcrSettings _powerOcrSettings;
|
||||
private Timer _delayedTimer;
|
||||
|
||||
private bool _isEnabled;
|
||||
|
||||
private Func<string, int> SendConfigMSG { get; }
|
||||
|
||||
public PowerOcrViewModel(
|
||||
ISettingsUtils settingsUtils,
|
||||
ISettingsRepository<GeneralSettings> settingsRepository,
|
||||
Func<string, int> ipcMSGCallBackFunc)
|
||||
{
|
||||
// To obtain the general settings configurations of PowerToys Settings.
|
||||
if (settingsRepository == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(settingsRepository));
|
||||
}
|
||||
|
||||
GeneralSettingsConfig = settingsRepository.SettingsConfig;
|
||||
|
||||
// To obtain the settings configurations of Fancy zones.
|
||||
if (settingsRepository == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(settingsRepository));
|
||||
}
|
||||
|
||||
_settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
|
||||
if (_settingsUtils.SettingsExists(PowerOcrSettings.ModuleName))
|
||||
{
|
||||
_powerOcrSettings = _settingsUtils.GetSettingsOrDefault<PowerOcrSettings>(PowerOcrSettings.ModuleName);
|
||||
}
|
||||
else
|
||||
{
|
||||
_powerOcrSettings = new PowerOcrSettings();
|
||||
}
|
||||
|
||||
_isEnabled = GeneralSettingsConfig.Enabled.PowerOCR;
|
||||
|
||||
// set the callback functions value to hangle outgoing IPC message.
|
||||
SendConfigMSG = ipcMSGCallBackFunc;
|
||||
|
||||
_delayedTimer = new Timer();
|
||||
_delayedTimer.Interval = SaveSettingsDelayInMs;
|
||||
_delayedTimer.Elapsed += DelayedTimer_Tick;
|
||||
_delayedTimer.AutoReset = false;
|
||||
}
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
set
|
||||
{
|
||||
if (_isEnabled != value)
|
||||
{
|
||||
_isEnabled = value;
|
||||
OnPropertyChanged(nameof(IsEnabled));
|
||||
|
||||
// Set the status of PowerOCR in the general settings
|
||||
GeneralSettingsConfig.Enabled.PowerOCR = value;
|
||||
var outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
|
||||
|
||||
SendConfigMSG(outgoing.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public HotkeySettings ActivationShortcut
|
||||
{
|
||||
get => _powerOcrSettings.Properties.ActivationShortcut;
|
||||
set
|
||||
{
|
||||
if (_powerOcrSettings.Properties.ActivationShortcut != value)
|
||||
{
|
||||
_powerOcrSettings.Properties.ActivationShortcut = value;
|
||||
OnPropertyChanged(nameof(ActivationShortcut));
|
||||
NotifySettingsChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ScheduleSavingOfSettings()
|
||||
{
|
||||
lock (_delayedActionLock)
|
||||
{
|
||||
if (_delayedTimer.Enabled)
|
||||
{
|
||||
_delayedTimer.Stop();
|
||||
}
|
||||
|
||||
_delayedTimer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void DelayedTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
lock (_delayedActionLock)
|
||||
{
|
||||
_delayedTimer.Stop();
|
||||
NotifySettingsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void NotifySettingsChanged()
|
||||
{
|
||||
// Using InvariantCulture as this is an IPC message
|
||||
SendConfigMSG(
|
||||
string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
|
||||
PowerOcrSettings.ModuleName,
|
||||
JsonSerializer.Serialize(_powerOcrSettings)));
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_delayedTimer.Dispose();
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,7 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
case "ImageResizer": StartupPage = typeof(Views.ImageResizerPage); break;
|
||||
case "KBM": StartupPage = typeof(Views.KeyboardManagerPage); break;
|
||||
case "MouseUtils": StartupPage = typeof(Views.MouseUtilsPage); break;
|
||||
case "PowerOCR": StartupPage = typeof(Views.PowerOcrPage); break;
|
||||
case "PowerRename": StartupPage = typeof(Views.PowerRenamePage); break;
|
||||
case "FileExplorer": StartupPage = typeof(Views.PowerPreviewPage); break;
|
||||
case "ShortcutGuide": StartupPage = typeof(Views.ShortcutGuidePage); break;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
BIN
src/settings-ui/Settings.UI/Assets/Modules/OOBE/PowerOCR.gif
Normal file
BIN
src/settings-ui/Settings.UI/Assets/Modules/OOBE/PowerOCR.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.1 MiB |
BIN
src/settings-ui/Settings.UI/Assets/Modules/PowerOCR.png
Normal file
BIN
src/settings-ui/Settings.UI/Assets/Modules/PowerOCR.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
@@ -15,6 +15,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Enums
|
||||
ImageResizer,
|
||||
KBM,
|
||||
MouseUtils,
|
||||
PowerOCR,
|
||||
PowerRename,
|
||||
Run,
|
||||
ShortcutGuide,
|
||||
|
||||
39
src/settings-ui/Settings.UI/OOBE/Views/OobePowerOCR.xaml
Normal file
39
src/settings-ui/Settings.UI/OOBE/Views/OobePowerOCR.xaml
Normal file
@@ -0,0 +1,39 @@
|
||||
<Page
|
||||
x:Class="Microsoft.PowerToys.Settings.UI.OOBE.Views.OobePowerOCR"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Microsoft.PowerToys.Settings.UI.OOBE.Views"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
|
||||
xmlns:toolkitcontrols="using:CommunityToolkit.WinUI.UI.Controls">
|
||||
|
||||
<controls:OOBEPageControl x:Uid="Oobe_PowerOCR"
|
||||
HeroImage="ms-appx:///Assets/Modules/OOBE/PowerOCR.gif">
|
||||
|
||||
<controls:OOBEPageControl.PageContent>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock x:Uid="Oobe_HowToUse"
|
||||
Style="{ThemeResource OobeSubtitleStyle}" />
|
||||
|
||||
<controls:ShortcutWithTextLabelControl x:Name="HotkeyControl" x:Uid="Oobe_PowerOCR_HowToUse" />
|
||||
|
||||
|
||||
<TextBlock x:Uid="Oobe_TipsAndTricks"
|
||||
Style="{ThemeResource OobeSubtitleStyle}"/>
|
||||
|
||||
<toolkitcontrols:MarkdownTextBlock Background="Transparent" x:Uid="Oobe_PowerOCR_TipsAndTricks" />
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="12" Margin="0,24,0,0">
|
||||
<Button x:Uid="OOBE_Settings"
|
||||
Click="SettingsLaunchButton_Click"/>
|
||||
<HyperlinkButton NavigateUri="https://aka.ms/PowerToysOverview_PowerOCR" Style="{StaticResource TextButtonStyle}">
|
||||
<TextBlock x:Uid="LearnMore_PowerOCR"
|
||||
TextWrapping="Wrap" />
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</controls:OOBEPageControl.PageContent>
|
||||
</controls:OOBEPageControl>
|
||||
</Page>
|
||||
47
src/settings-ui/Settings.UI/OOBE/Views/OobePowerOCR.xaml.cs
Normal file
47
src/settings-ui/Settings.UI/OOBE/Views/OobePowerOCR.xaml.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
// 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.Threading;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
|
||||
using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
|
||||
using Microsoft.PowerToys.Settings.UI.Views;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
{
|
||||
public sealed partial class OobePowerOCR : Page
|
||||
{
|
||||
public OobePowerToysModule ViewModel { get; set; }
|
||||
|
||||
public OobePowerOCR()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.PowerOCR]);
|
||||
DataContext = ViewModel;
|
||||
}
|
||||
|
||||
private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
if (OobeShellPage.OpenMainWindowCallback != null)
|
||||
{
|
||||
OobeShellPage.OpenMainWindowCallback(typeof(PowerOcrPage));
|
||||
}
|
||||
|
||||
ViewModel.LogOpeningSettingsEvent();
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
ViewModel.LogOpeningModuleEvent();
|
||||
HotkeyControl.Keys = SettingsRepository<PowerOcrSettings>.GetInstance(new SettingsUtils()).SettingsConfig.Properties.ActivationShortcut.GetKeysList();
|
||||
}
|
||||
|
||||
protected override void OnNavigatedFrom(NavigationEventArgs e)
|
||||
{
|
||||
ViewModel.LogClosingModuleEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,6 +116,14 @@
|
||||
</muxc:NavigationViewItem.Icon>
|
||||
</muxc:NavigationViewItem>
|
||||
|
||||
<muxc:NavigationViewItem x:Uid="Shell_PowerOCR"
|
||||
Tag="PowerOCR">
|
||||
<muxc:NavigationViewItem.Icon>
|
||||
<BitmapIcon UriSource="ms-appx:///Assets/FluentIcons/FluentIconsPowerOCR.png"
|
||||
ShowAsMonochrome="False" />
|
||||
</muxc:NavigationViewItem.Icon>
|
||||
</muxc:NavigationViewItem>
|
||||
|
||||
<muxc:NavigationViewItem x:Uid="Shell_PowerRename"
|
||||
Tag="PowerRename">
|
||||
<muxc:NavigationViewItem.Icon>
|
||||
|
||||
@@ -102,6 +102,11 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
ModuleName = "MouseUtils",
|
||||
IsNew = true,
|
||||
});
|
||||
Modules.Insert((int)PowerToysModules.PowerOCR, new OobePowerToysModule()
|
||||
{
|
||||
ModuleName = "PowerOCR",
|
||||
IsNew = true,
|
||||
});
|
||||
Modules.Insert((int)PowerToysModules.PowerRename, new OobePowerToysModule()
|
||||
{
|
||||
ModuleName = "PowerRename",
|
||||
@@ -170,6 +175,7 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
case "ImageResizer": NavigationFrame.Navigate(typeof(OobeImageResizer)); break;
|
||||
case "KBM": NavigationFrame.Navigate(typeof(OobeKBM)); break;
|
||||
case "PowerRename": NavigationFrame.Navigate(typeof(OobePowerRename)); break;
|
||||
case "PowerOCR": NavigationFrame.Navigate(typeof(OobePowerOCR)); break;
|
||||
case "FileExplorer": NavigationFrame.Navigate(typeof(OobeFileExplorer)); break;
|
||||
case "ShortcutGuide": NavigationFrame.Navigate(typeof(OobeShortcutGuide)); break;
|
||||
case "VideoConference": NavigationFrame.Navigate(typeof(OobeVideoConference)); break;
|
||||
|
||||
@@ -35,10 +35,6 @@
|
||||
<Optimize>true</Optimize>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="OobeWindow.xaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Assets\SplashScreen.scale-200.png" />
|
||||
<Content Include="Assets\LockScreenLogo.scale-200.png" />
|
||||
@@ -68,14 +64,14 @@
|
||||
<ProjectReference Include="..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
||||
<ProjectReference Include="..\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="OobeWindow.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="icon.ico">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="OOBE\Views\OobePowerOCR.xaml">
|
||||
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -2164,6 +2164,39 @@ From there, simply click on one of the supported files in the File Explorer and
|
||||
<data name="AlwaysOnTop_RoundCorners.Content" xml:space="preserve">
|
||||
<value>Enable round corners</value>
|
||||
</data>
|
||||
<data name="LearnMore_PowerOcr.Text" xml:space="preserve">
|
||||
<value>Learn more about PowerOCR</value>
|
||||
</data>
|
||||
<data name="PowerOCR.ModuleDescription" xml:space="preserve">
|
||||
<value>PowerOCR is a convenient way to copy text from anywhere on screen</value>
|
||||
</data>
|
||||
<data name="PowerOCR.ModuleTitle" xml:space="preserve">
|
||||
<value>PowerOCR</value>
|
||||
</data>
|
||||
<data name="PowerOCR_EnableToggleControl_HeaderText.Header" xml:space="preserve">
|
||||
<value>Enable PowerOCR</value>
|
||||
</data>
|
||||
<data name="Shell_PowerOcr.Content" xml:space="preserve">
|
||||
<value>PowerOCR</value>
|
||||
</data>
|
||||
<data name="Launch_PowerOCR.Content" xml:space="preserve">
|
||||
<value>Launch PowerOCR</value>
|
||||
</data>
|
||||
<data name="Oobe_PowerOCR.Title" xml:space="preserve">
|
||||
<value>PowerOCR</value>
|
||||
</data>
|
||||
<data name="Oobe_PowerOCR_HowToUse.Text" xml:space="preserve">
|
||||
<value>to open PowerOCR and then selecting a region to copy the text from.</value>
|
||||
</data>
|
||||
<data name="Oobe_PowerOCR_TipsAndTricks.Text" xml:space="preserve">
|
||||
<value>Hold the shift key to move the selection region around.</value>
|
||||
</data>
|
||||
<data name="PowerOCR.SecondaryLinksHeader" xml:space="preserve">
|
||||
<value>Attribution</value>
|
||||
</data>
|
||||
<data name="Oobe_PowerOCR.Description" xml:space="preserve">
|
||||
<value>PowerOCR works like Snipping Tool, but copies the text out of the selected region using OCR and puts it on the clipboard.</value>
|
||||
</data>
|
||||
<data name="FileExplorerPreview_ToggleSwitch_Monaco_Try_Format.Description" xml:space="preserve">
|
||||
<value>Applies to json and xml. Files remain unchanged.</value>
|
||||
</data>
|
||||
|
||||
44
src/settings-ui/Settings.UI/Views/PowerOcrPage.xaml
Normal file
44
src/settings-ui/Settings.UI/Views/PowerOcrPage.xaml
Normal file
@@ -0,0 +1,44 @@
|
||||
<Page
|
||||
x:Class="Microsoft.PowerToys.Settings.UI.Views.PowerOcrPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Microsoft.PowerToys.Settings.UI.Controls"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.UI.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
AutomationProperties.LandmarkType="Main"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<controls:SettingsPageControl x:Uid="PowerOCR" ModuleImageSource="ms-appx:///Assets/Modules/PowerOCR.png">
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel Orientation="Vertical">
|
||||
|
||||
<controls:Setting x:Uid="PowerOCR_EnableToggleControl_HeaderText">
|
||||
<controls:Setting.Icon>
|
||||
<BitmapIcon ShowAsMonochrome="False" UriSource="ms-appx:///Assets/FluentIcons/FluentIconsPowerOcr.png" />
|
||||
</controls:Setting.Icon>
|
||||
<controls:Setting.ActionContent>
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
|
||||
<controls:SettingsGroup x:Uid="Shortcut" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsEnabled}">
|
||||
<controls:Setting x:Uid="Activation_Shortcut" Icon="">
|
||||
<controls:Setting.ActionContent>
|
||||
<controls:ShortcutControl MinWidth="{StaticResource SettingActionControlMinWidth}" HotkeySettings="{x:Bind Path=ViewModel.ActivationShortcut, Mode=TwoWay}" />
|
||||
</controls:Setting.ActionContent>
|
||||
</controls:Setting>
|
||||
|
||||
</controls:SettingsGroup>
|
||||
|
||||
</StackPanel>
|
||||
</controls:SettingsPageControl.ModuleContent>
|
||||
|
||||
<controls:SettingsPageControl.PrimaryLinks>
|
||||
<controls:PageLink x:Uid="LearnMore_PowerOcr" Link="https://aka.ms/PowerToysOverview_PowerOCR" />
|
||||
</controls:SettingsPageControl.PrimaryLinks>
|
||||
<controls:SettingsPageControl.SecondaryLinks>
|
||||
<controls:PageLink Text="Based upon Joseph Finney's Text Grab" Link="https://github.com/TheJoeFin/Text-Grab"/>
|
||||
</controls:SettingsPageControl.SecondaryLinks>
|
||||
</controls:SettingsPageControl>
|
||||
</Page>
|
||||
26
src/settings-ui/Settings.UI/Views/PowerOcrPage.xaml.cs
Normal file
26
src/settings-ui/Settings.UI/Views/PowerOcrPage.xaml.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
// 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 Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.ViewModels;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
{
|
||||
public sealed partial class PowerOcrPage : Page
|
||||
{
|
||||
private PowerOcrViewModel ViewModel { get; set; }
|
||||
|
||||
public PowerOcrPage()
|
||||
{
|
||||
var settingsUtils = new SettingsUtils();
|
||||
ViewModel = new PowerOcrViewModel(
|
||||
settingsUtils,
|
||||
SettingsRepository<GeneralSettings>.GetInstance(settingsUtils),
|
||||
ShellPage.SendDefaultIPCMessage);
|
||||
DataContext = ViewModel;
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,6 +120,14 @@
|
||||
ShowAsMonochrome="False" />
|
||||
</muxc:NavigationViewItem.Icon>
|
||||
</muxc:NavigationViewItem>
|
||||
|
||||
<muxc:NavigationViewItem x:Uid="Shell_PowerOcr"
|
||||
helpers:NavHelper.NavigateTo="views:PowerOcrPage">
|
||||
<muxc:NavigationViewItem.Icon>
|
||||
<BitmapIcon UriSource="ms-appx:///Assets/FluentIcons/FluentIconsPowerOcr.png"
|
||||
ShowAsMonochrome="False" />
|
||||
</muxc:NavigationViewItem.Icon>
|
||||
</muxc:NavigationViewItem>
|
||||
|
||||
<muxc:NavigationViewItem x:Uid="Shell_PowerLauncher"
|
||||
helpers:NavHelper.NavigateTo="views:PowerLauncherPage">
|
||||
|
||||
@@ -12,6 +12,7 @@ std::vector<std::wstring> processes =
|
||||
L"PowerToys.KeyboardManagerEngine.exe",
|
||||
L"PowerToys.KeyboardManagerEditor.exe",
|
||||
L"PowerToys.PowerLauncher.exe",
|
||||
L"PowerToys.PowerOCR.exe",
|
||||
L"PowerToys.ShortcutGuide.exe",
|
||||
L"PowerToys.PowerRename.exe",
|
||||
L"PowerToys.ImageResizer.exe",
|
||||
|
||||
Reference in New Issue
Block a user