diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b51781c58e1..78affbb16f1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -47,6 +47,7 @@ body: multiple: true options: - General + - Advanced Paste - Always on Top - Awake - ColorPicker @@ -64,7 +65,6 @@ body: - Keyboard Manager - Mouse Utilities - Mouse Without Borders - - Paste as Plain Text - Peek - PowerRename - PowerToys Run diff --git a/.github/ISSUE_TEMPLATE/translation_issue.yml b/.github/ISSUE_TEMPLATE/translation_issue.yml index 11517c195be..88114730c68 100644 --- a/.github/ISSUE_TEMPLATE/translation_issue.yml +++ b/.github/ISSUE_TEMPLATE/translation_issue.yml @@ -21,6 +21,7 @@ body: label: Utility with translation issue options: - General + - Advanced Paste - Always on Top - Awake - ColorPicker @@ -38,7 +39,6 @@ body: - Keyboard Manager - Mouse Utilities - Mouse Without Borders - - Paste as Plain Text - Peek - PowerRename - PowerToys Run diff --git a/.github/actions/spell-check/allow/code.txt b/.github/actions/spell-check/allow/code.txt index 7c6a022ac5d..3afbb1f05e4 100644 --- a/.github/actions/spell-check/allow/code.txt +++ b/.github/actions/spell-check/allow/code.txt @@ -25,6 +25,7 @@ WHITEONBLACK AYUV bak +Bcl exa exabyte Gbits diff --git a/.github/actions/spell-check/allow/names.txt b/.github/actions/spell-check/allow/names.txt index 16a1caa5b22..c04863a1c61 100644 --- a/.github/actions/spell-check/allow/names.txt +++ b/.github/actions/spell-check/allow/names.txt @@ -10,7 +10,6 @@ markdownpreviewhandler mousewithoutborders mwb oobe -pasteplain poweraccent powerlauncher POWEROCR @@ -144,6 +143,7 @@ Controlz cortana fancymouse firefox +gpt Inkscape Markdig modernwpf @@ -152,6 +152,7 @@ mozilla mspaint Newtonsoft onenote +openai Quickime regedit roslyn diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 785ae7f40f0..a3ceb9fe457 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -894,6 +894,7 @@ MINIMIZEBOX MINIMIZEEND MINIMIZESTART miniz +Mip Miracast mjpg mkdn diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index 8feaeb6c610..03a30fbe8dd 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -45,7 +45,9 @@ "PowerToys.PowerOCR.dll", "PowerToys.PowerOCR.exe", - "PowerToys.PastePlainModuleInterface.dll", + "PowerToys.AdvancedPasteModuleInterface.dll", + "WinUI3Apps\\PowerToys.AdvancedPaste.exe", + "WinUI3Apps\\PowerToys.AdvancedPaste.dll", "PowerToys.AwakeModuleInterface.dll", "PowerToys.Awake.exe", @@ -303,6 +305,7 @@ "MessagePack.Annotations.dll", "MessagePack.dll", "Nerdbank.Streams.dll", + "WinUI3Apps\\ReverseMarkdown.dll", "WinUI3Apps\\SharpCompress.dll", "WinUI3Apps\\ZstdSharp.dll", "ColorCode.Core.dll", diff --git a/Directory.Packages.props b/Directory.Packages.props index 00c6b8f96af..81266397997 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,6 +4,7 @@ + @@ -20,6 +21,7 @@ + @@ -56,6 +58,7 @@ + diff --git a/NOTICE.md b/NOTICE.md index c62bee93f61..97d24cee979 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -1297,6 +1297,7 @@ EXHIBIT A -Mozilla Public License. ## NuGet Packages used by PowerToys - Appium.WebDriver 4.4.5 +- Azure.AI.OpenAI 1.0.0-beta.12 - CommunityToolkit.Mvvm 8.2.2 - CommunityToolkit.WinUI.Animations 8.0.240109 - CommunityToolkit.WinUI.Collections 8.0.240109 @@ -1341,6 +1342,7 @@ EXHIBIT A -Mozilla Public License. - MSTest.TestFramework 3.2.0 - NLog.Extensions.Logging 5.3.8 - NLog.Schema 5.2.8 +- ReverseMarkdown 4.1.0 - ScipBe.Common.Office.OneNote 3.0.1 - SharpCompress 0.37.2 - StreamJsonRpc 2.14.24 diff --git a/PowerToys.sln b/PowerToys.sln index ce05c576ff6..5d881d3e30d 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -512,9 +512,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseJumpUI", "src\modules\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseJumpUI.UnitTests", "src\modules\MouseUtils\MouseJumpUI.UnitTests\MouseJumpUI.UnitTests.csproj", "{D9C5DE64-6849-4278-91AD-9660AECF2876}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pasteplain", "pasteplain", "{9873BA05-4C41-4819-9283-CF45D795431B}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AdvancedPaste", "AdvancedPaste", "{9873BA05-4C41-4819-9283-CF45D795431B}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PastePlainModuleInterface", "src\modules\pasteplain\PastePlainModuleInterface\PastePlainModuleInterface.vcxproj", "{FC373B24-3293-453C-AAF5-CF2909DCEE6A}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AdvancedPasteModuleInterface", "src\modules\AdvancedPaste\AdvancedPasteModuleInterface\AdvancedPasteModuleInterface.vcxproj", "{FC373B24-3293-453C-AAF5-CF2909DCEE6A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AllExperiments", "src\common\AllExperiments\AllExperiments.csproj", "{9CE59ED5-7087-4353-88EB-788038A73CEC}" EndProject @@ -566,6 +566,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileLocksmithContextMenu", EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "FileLocksmithLib", "src\modules\FileLocksmith\FileLocksmithLib\FileLocksmithLib.vcxproj", "{9D52FD25-EF90-4F9A-A015-91EFC5DAF54F}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdvancedPaste", "src\modules\AdvancedPaste\AdvancedPaste\AdvancedPaste.csproj", "{C32D254F-7597-4CBE-BF74-D922D81CDF29}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hosts", "src\modules\Hosts\Hosts\Hosts.csproj", "{02DD46D3-F761-47D9-8894-2D6DA0124650}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RegistryPreview", "src\modules\registrypreview\RegistryPreview\RegistryPreview.csproj", "{8E23E173-7127-4A5F-9F93-3049F2B68047}" @@ -2499,6 +2501,24 @@ Global {9D52FD25-EF90-4F9A-A015-91EFC5DAF54F}.Release|x64.Build.0 = Release|x64 {9D52FD25-EF90-4F9A-A015-91EFC5DAF54F}.Release|x86.ActiveCfg = Release|x64 {9D52FD25-EF90-4F9A-A015-91EFC5DAF54F}.Release|x86.Build.0 = Release|x64 + {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Debug|ARM64.Build.0 = Debug|ARM64 + {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Debug|x64.ActiveCfg = Debug|x64 + {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Debug|x64.Build.0 = Debug|x64 + {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Debug|x64.Deploy.0 = Debug|x64 + {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Debug|x86.ActiveCfg = Debug|x64 + {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Debug|x86.Build.0 = Debug|x64 + {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Debug|x86.Deploy.0 = Debug|x64 + {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Release|ARM64.ActiveCfg = Release|ARM64 + {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Release|ARM64.Build.0 = Release|ARM64 + {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Release|ARM64.Deploy.0 = Release|ARM64 + {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Release|x64.ActiveCfg = Release|x64 + {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Release|x64.Build.0 = Release|x64 + {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Release|x64.Deploy.0 = Release|x64 + {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Release|x86.ActiveCfg = Release|x64 + {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Release|x86.Build.0 = Release|x64 + {C32D254F-7597-4CBE-BF74-D922D81CDF29}.Release|x86.Deploy.0 = Release|x64 {02DD46D3-F761-47D9-8894-2D6DA0124650}.Debug|ARM64.ActiveCfg = Debug|ARM64 {02DD46D3-F761-47D9-8894-2D6DA0124650}.Debug|ARM64.Build.0 = Debug|ARM64 {02DD46D3-F761-47D9-8894-2D6DA0124650}.Debug|x64.ActiveCfg = Debug|x64 @@ -2791,6 +2811,7 @@ Global {0014D652-901F-4456-8D65-06FC5F997FB0} = {4C0D0746-BE5B-49EE-BD5D-A7811628AE8B} {799A50D8-DE89-4ED1-8FF8-AD5A9ED8C0CA} = {AB82E5DD-C32D-4F28-9746-2C780846188E} {9D52FD25-EF90-4F9A-A015-91EFC5DAF54F} = {AB82E5DD-C32D-4F28-9746-2C780846188E} + {C32D254F-7597-4CBE-BF74-D922D81CDF29} = {9873BA05-4C41-4819-9283-CF45D795431B} {02DD46D3-F761-47D9-8894-2D6DA0124650} = {F05E590D-AD46-42BE-9C25-6A63ADD2E3EA} {8E23E173-7127-4A5F-9F93-3049F2B68047} = {929C1324-22E8-4412-A9A8-80E85F3985A5} {DFF88D16-D36F-40A4-A955-CDCAA76EF7B8} = {538ED0BB-B863-4B20-98CC-BCDF7FA0B68A} diff --git a/doc/images/icons/AdvancedPaste.png b/doc/images/icons/AdvancedPaste.png new file mode 100644 index 00000000000..ff187321754 Binary files /dev/null and b/doc/images/icons/AdvancedPaste.png differ diff --git a/doc/images/icons/Paste As Plain Text.png b/doc/images/icons/Paste As Plain Text.png deleted file mode 100644 index 3ce90ef52d4..00000000000 Binary files a/doc/images/icons/Paste As Plain Text.png and /dev/null differ diff --git a/doc/images/overview/AdvancedPaste_large.png b/doc/images/overview/AdvancedPaste_large.png new file mode 100644 index 00000000000..2a3226ace7b Binary files /dev/null and b/doc/images/overview/AdvancedPaste_large.png differ diff --git a/doc/images/overview/AdvancedPaste_small.png b/doc/images/overview/AdvancedPaste_small.png new file mode 100644 index 00000000000..63622fa0c5a Binary files /dev/null and b/doc/images/overview/AdvancedPaste_small.png differ diff --git a/doc/images/overview/Original/AdvancedPaste.png b/doc/images/overview/Original/AdvancedPaste.png new file mode 100644 index 00000000000..0085fe170cb Binary files /dev/null and b/doc/images/overview/Original/AdvancedPaste.png differ diff --git a/doc/images/overview/Original/PasteAsPlainText.png b/doc/images/overview/Original/PasteAsPlainText.png deleted file mode 100644 index 68f3c67e310..00000000000 Binary files a/doc/images/overview/Original/PasteAsPlainText.png and /dev/null differ diff --git a/doc/images/overview/PasteAsPlainText_large.png b/doc/images/overview/PasteAsPlainText_large.png deleted file mode 100644 index dfc83a2f33f..00000000000 Binary files a/doc/images/overview/PasteAsPlainText_large.png and /dev/null differ diff --git a/doc/images/overview/PasteAsPlainText_small.png b/doc/images/overview/PasteAsPlainText_small.png deleted file mode 100644 index 866c49933a1..00000000000 Binary files a/doc/images/overview/PasteAsPlainText_small.png and /dev/null differ diff --git a/installer/PowerToysSetup/AdvancedPaste.wxs b/installer/PowerToysSetup/AdvancedPaste.wxs new file mode 100644 index 00000000000..a865ddbf6c0 --- /dev/null +++ b/installer/PowerToysSetup/AdvancedPaste.wxs @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetup/Common.wxi b/installer/PowerToysSetup/Common.wxi index 4a1c93fc8ea..4d64a0c63ce 100644 --- a/installer/PowerToysSetup/Common.wxi +++ b/installer/PowerToysSetup/Common.wxi @@ -15,7 +15,7 @@ - + diff --git a/installer/PowerToysSetup/PowerToysInstaller.wixproj b/installer/PowerToysSetup/PowerToysInstaller.wixproj index 926ba624dce..0d3487ccce3 100644 --- a/installer/PowerToysSetup/PowerToysInstaller.wixproj +++ b/installer/PowerToysSetup/PowerToysInstaller.wixproj @@ -31,6 +31,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil Always + call move /Y ..\..\..\AdvancedPaste.wxs.bk ..\..\..\AdvancedPaste.wxs call move /Y ..\..\..\Awake.wxs.bk ..\..\..\Awake.wxs call move /Y ..\..\..\BaseApplications.wxs.bk ..\..\..\BaseApplications.wxs call move /Y ..\..\..\ColorPicker.wxs.bk ..\..\..\ColorPicker.wxs @@ -102,6 +103,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil + diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index 1fe1c838d9b..aab1ed15204 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -72,7 +72,7 @@ - + @@ -161,6 +161,9 @@ Installed AND (REMOVE="ALL") + + Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") + Installed AND (NOT UPGRADINGPRODUCTCODE) AND (REMOVE="ALL") @@ -285,6 +288,14 @@ DllEntry="UpgradeCommandNotFoundModuleCA" /> + + #include #include +#include #include #include @@ -433,6 +434,31 @@ UINT __stdcall RemoveWindowsServiceByName(std::wstring serviceName) return ERROR_SUCCESS; } +UINT __stdcall UnsetAdvancedPasteAPIKeyCA(MSIHANDLE hInstall) +{ + HRESULT hr = S_OK; + UINT er = ERROR_SUCCESS; + + try + { + winrt::Windows::Security::Credentials::PasswordVault vault; + winrt::Windows::Security::Credentials::PasswordCredential cred; + + hr = WcaInitialize(hInstall, "UnsetAdvancedPasteAPIKey"); + ExitOnFailure(hr, "Failed to initialize"); + + cred = vault.Retrieve(L"https://platform.openai.com/api-keys", L"PowerToys_AdvancedPaste_OpenAIKey"); + vault.Remove(cred); + } + catch (...) + { + } + +LExit: + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; + return WcaFinalize(er); +} + UINT __stdcall UninstallCommandNotFoundModuleCA(MSIHANDLE hInstall) { HRESULT hr = S_OK; @@ -1051,9 +1077,10 @@ UINT __stdcall TerminateProcessesCA(MSIHANDLE hInstall) } processes.resize(bytes / sizeof(processes[0])); - std::array processesToTerminate = { + std::array processesToTerminate = { L"PowerToys.PowerLauncher.exe", L"PowerToys.Settings.exe", + L"PowerToys.AdvancedPaste.exe", L"PowerToys.Awake.exe", L"PowerToys.FancyZones.exe", L"PowerToys.FancyZonesEditor.exe", diff --git a/installer/PowerToysSetupCustomActions/CustomAction.def b/installer/PowerToysSetupCustomActions/CustomAction.def index 7a4929935c3..a7d2f68f72e 100644 --- a/installer/PowerToysSetupCustomActions/CustomAction.def +++ b/installer/PowerToysSetupCustomActions/CustomAction.def @@ -24,4 +24,5 @@ EXPORTS UninstallEmbeddedMSIXCA UninstallServicesCA UninstallCommandNotFoundModuleCA - UpgradeCommandNotFoundModuleCA \ No newline at end of file + UpgradeCommandNotFoundModuleCA + UnsetAdvancedPasteAPIKeyCA diff --git a/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj index 46c20790a89..b8efdc3385f 100644 --- a/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj +++ b/installer/PowerToysSetupCustomActions/PowerToysSetupCustomActions.vcxproj @@ -48,6 +48,7 @@ call cmd /C "copy ""$(ProjectDir)DepsFilesLists.h"" ""$(ProjectDir)DepsFilesLists.h.bk""" + call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\AdvancedPaste.wxs"" ""$(ProjectDir)..\PowerToysSetup\AdvancedPaste.wxs.bk"""" call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\Awake.wxs"" ""$(ProjectDir)..\PowerToysSetup\Awake.wxs.bk"""" call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\BaseApplications.wxs"" ""$(ProjectDir)..\PowerToysSetup\BaseApplications.wxs.bk"""" call cmd /C "copy ""$(ProjectDir)..\PowerToysSetup\ColorPicker.wxs"" ""$(ProjectDir)..\PowerToysSetup\ColorPicker.wxs.bk"""" diff --git a/src/common/Common.UI/SettingsDeepLink.cs b/src/common/Common.UI/SettingsDeepLink.cs index 1d40ff70642..c3a81a49a1b 100644 --- a/src/common/Common.UI/SettingsDeepLink.cs +++ b/src/common/Common.UI/SettingsDeepLink.cs @@ -30,6 +30,7 @@ public enum SettingsWindow CropAndLock, EnvironmentVariables, Dashboard, + AdvancedPaste, } private static string SettingsWindowNameToString(SettingsWindow value) @@ -74,6 +75,8 @@ private static string SettingsWindowNameToString(SettingsWindow value) return "EnvironmentVariables"; case SettingsWindow.Dashboard: return "Dashboard"; + case SettingsWindow.AdvancedPaste: + return "AdvancedPaste"; default: { return string.Empty; diff --git a/src/common/GPOWrapper/GPOWrapper.cpp b/src/common/GPOWrapper/GPOWrapper.cpp index a8eb3a5513d..61e9bd3e84e 100644 --- a/src/common/GPOWrapper/GPOWrapper.cpp +++ b/src/common/GPOWrapper/GPOWrapper.cpp @@ -124,9 +124,9 @@ namespace winrt::PowerToys::GPOWrapper::implementation { return static_cast(powertoys_gpo::getConfiguredTextExtractorEnabledValue()); } - GpoRuleConfigured GPOWrapper::GetConfiguredPastePlainEnabledValue() + GpoRuleConfigured GPOWrapper::GetConfiguredAdvancedPasteEnabledValue() { - return static_cast(powertoys_gpo::getConfiguredPastePlainEnabledValue()); + return static_cast(powertoys_gpo::getConfiguredAdvancedPasteEnabledValue()); } GpoRuleConfigured GPOWrapper::GetConfiguredVideoConferenceMuteEnabledValue() { diff --git a/src/common/GPOWrapper/GPOWrapper.h b/src/common/GPOWrapper/GPOWrapper.h index 981c6dc50e6..e4d5b8185ce 100644 --- a/src/common/GPOWrapper/GPOWrapper.h +++ b/src/common/GPOWrapper/GPOWrapper.h @@ -38,7 +38,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation static GpoRuleConfigured GetConfiguredScreenRulerEnabledValue(); static GpoRuleConfigured GetConfiguredShortcutGuideEnabledValue(); static GpoRuleConfigured GetConfiguredTextExtractorEnabledValue(); - static GpoRuleConfigured GetConfiguredPastePlainEnabledValue(); + static GpoRuleConfigured GetConfiguredAdvancedPasteEnabledValue(); static GpoRuleConfigured GetConfiguredVideoConferenceMuteEnabledValue(); static GpoRuleConfigured GetConfiguredPeekEnabledValue(); static GpoRuleConfigured GetDisableNewUpdateToastValue(); diff --git a/src/common/GPOWrapper/GPOWrapper.idl b/src/common/GPOWrapper/GPOWrapper.idl index eed2f2f393c..9b4054dee72 100644 --- a/src/common/GPOWrapper/GPOWrapper.idl +++ b/src/common/GPOWrapper/GPOWrapper.idl @@ -42,7 +42,7 @@ namespace PowerToys static GpoRuleConfigured GetConfiguredScreenRulerEnabledValue(); static GpoRuleConfigured GetConfiguredShortcutGuideEnabledValue(); static GpoRuleConfigured GetConfiguredTextExtractorEnabledValue(); - static GpoRuleConfigured GetConfiguredPastePlainEnabledValue(); + static GpoRuleConfigured GetConfiguredAdvancedPasteEnabledValue(); static GpoRuleConfigured GetConfiguredVideoConferenceMuteEnabledValue(); static GpoRuleConfigured GetConfiguredPeekEnabledValue(); static GpoRuleConfigured GetDisableNewUpdateToastValue(); diff --git a/src/common/GPOWrapperProjection/GPOWrapper.cs b/src/common/GPOWrapperProjection/GPOWrapper.cs index 5e1ef147484..f0c0b8421cf 100644 --- a/src/common/GPOWrapperProjection/GPOWrapper.cs +++ b/src/common/GPOWrapperProjection/GPOWrapper.cs @@ -47,9 +47,9 @@ public static GpoRuleConfigured GetConfiguredTextExtractorEnabledValue() return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredTextExtractorEnabledValue(); } - public static GpoRuleConfigured GetConfiguredPastePlainEnabledValue() + public static GpoRuleConfigured GetConfiguredAdvancedPasteEnabledValue() { - return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredPastePlainEnabledValue(); + return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredAdvancedPasteEnabledValue(); } public static GpoRuleConfigured GetConfiguredPeekEnabledValue() diff --git a/src/common/ManagedCommon/ModuleType.cs b/src/common/ManagedCommon/ModuleType.cs index 3a489711b08..de57f5138c5 100644 --- a/src/common/ManagedCommon/ModuleType.cs +++ b/src/common/ManagedCommon/ModuleType.cs @@ -6,6 +6,7 @@ namespace ManagedCommon { public enum ModuleType { + AdvancedPaste, AlwaysOnTop, Awake, ColorPicker, @@ -21,7 +22,6 @@ public enum ModuleType MouseJump, MousePointerCrosshairs, MouseWithoutBorders, - PastePlain, Peek, PowerRename, PowerLauncher, diff --git a/src/common/interop/interop.cpp b/src/common/interop/interop.cpp index a870985e5ca..6b5a86ec9ba 100644 --- a/src/common/interop/interop.cpp +++ b/src/common/interop/interop.cpp @@ -204,6 +204,18 @@ public return gcnew String(CommonSharedConstants::SHOW_COLOR_PICKER_SHARED_EVENT); } + static String ^ ShowAdvancedPasteSharedEvent() { + return gcnew String(CommonSharedConstants::SHOW_ADVANCED_PASTE_SHARED_EVENT); + } + + static String ^ AdvancedPasteMarkdownEvent() { + return gcnew String(CommonSharedConstants::ADVANCED_PASTE_MARKDOWN_EVENT); + } + + static String ^ AdvancedPasteJsonEvent() { + return gcnew String(CommonSharedConstants::ADVANCED_PASTE_JSON_EVENT); + } + static String ^ ShowPowerOCRSharedEvent() { return gcnew String(CommonSharedConstants::SHOW_POWEROCR_SHARED_EVENT); } diff --git a/src/common/interop/shared_constants.h b/src/common/interop/shared_constants.h index 441621ecb24..675b6f7830b 100644 --- a/src/common/interop/shared_constants.h +++ b/src/common/interop/shared_constants.h @@ -25,6 +25,13 @@ namespace CommonSharedConstants const wchar_t COLOR_PICKER_SEND_SETTINGS_TELEMETRY_EVENT[] = L"Local\\ColorPickerSettingsTelemetryEvent-6c7071d8-4014-46ec-b687-913bd8a422f1"; + // Path to the event used to show Advanced Paste UI + const wchar_t SHOW_ADVANCED_PASTE_SHARED_EVENT[] = L"Local\\ShowAdvancedPasteEvent-9a46be2a-3e05-4186-b56b-4ae986ef2526"; + + const wchar_t ADVANCED_PASTE_MARKDOWN_EVENT[] = L"Local\\AdvancedPasteJsonEvent-a18c0798-3ee6-4fc5-bb9f-114c57ac0d47"; + + const wchar_t ADVANCED_PASTE_JSON_EVENT[] = L"Local\\AdvancedPasteJsonEvent-9ed021ab-b711-4cf3-9f33-135a698a9d21"; + // Path to the event used to show Color Picker const wchar_t SHOW_COLOR_PICKER_SHARED_EVENT[] = L"Local\\ShowColorPickerEvent-8c46be2a-3e05-4186-b56b-4ae986ef2525"; diff --git a/src/common/utils/gpo.h b/src/common/utils/gpo.h index d1ded85f64a..adb8ff57dc1 100644 --- a/src/common/utils/gpo.h +++ b/src/common/utils/gpo.h @@ -51,7 +51,7 @@ namespace powertoys_gpo { const std::wstring POLICY_CONFIGURE_ENABLED_SCREEN_RULER = L"ConfigureEnabledUtilityScreenRuler"; const std::wstring POLICY_CONFIGURE_ENABLED_SHORTCUT_GUIDE = L"ConfigureEnabledUtilityShortcutGuide"; const std::wstring POLICY_CONFIGURE_ENABLED_TEXT_EXTRACTOR = L"ConfigureEnabledUtilityTextExtractor"; - const std::wstring POLICY_CONFIGURE_ENABLED_PASTE_PLAIN = L"ConfigureEnabledUtilityPastePlain"; + const std::wstring POLICY_CONFIGURE_ENABLED_ADVANCED_PASTE = L"ConfigureEnabledUtilityAdvancedPaste"; const std::wstring POLICY_CONFIGURE_ENABLED_VIDEO_CONFERENCE_MUTE = L"ConfigureEnabledUtilityVideoConferenceMute"; const std::wstring POLICY_CONFIGURE_ENABLED_REGISTRY_PREVIEW = L"ConfigureEnabledUtilityRegistryPreview"; const std::wstring POLICY_CONFIGURE_ENABLED_MOUSE_WITHOUT_BORDERS = L"ConfigureEnabledUtilityMouseWithoutBorders"; @@ -361,9 +361,9 @@ namespace powertoys_gpo { return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_TEXT_EXTRACTOR); } - inline gpo_rule_configured_t getConfiguredPastePlainEnabledValue() + inline gpo_rule_configured_t getConfiguredAdvancedPasteEnabledValue() { - return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_PASTE_PLAIN); + return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_ADVANCED_PASTE); } inline gpo_rule_configured_t getConfiguredVideoConferenceMuteEnabledValue() diff --git a/src/dsc/Microsoft.PowerToys.Configure/examples/disableAllModules.dsc.yaml b/src/dsc/Microsoft.PowerToys.Configure/examples/disableAllModules.dsc.yaml index 248ee7c2765..8fa39a1050a 100644 --- a/src/dsc/Microsoft.PowerToys.Configure/examples/disableAllModules.dsc.yaml +++ b/src/dsc/Microsoft.PowerToys.Configure/examples/disableAllModules.dsc.yaml @@ -5,6 +5,8 @@ properties: directives: description: Configure PowerToys settings: + AdvancedPaste: + Enabled: false AlwaysOnTop: Enabled: false Awake: @@ -63,8 +65,6 @@ properties: Enabled: false Hosts: Enabled: false - PastePlain: - Enabled: false RegistryPreview: Enabled: false diff --git a/src/dsc/Microsoft.PowerToys.Configure/examples/enableAllModules.dsc.yaml b/src/dsc/Microsoft.PowerToys.Configure/examples/enableAllModules.dsc.yaml index 3e1a05d25e4..a7b69fd423e 100644 --- a/src/dsc/Microsoft.PowerToys.Configure/examples/enableAllModules.dsc.yaml +++ b/src/dsc/Microsoft.PowerToys.Configure/examples/enableAllModules.dsc.yaml @@ -5,6 +5,8 @@ properties: directives: description: Configure PowerToys settings: + AdvancedPaste: + Enabled: true AlwaysOnTop: Enabled: true Awake: @@ -63,8 +65,6 @@ properties: Enabled: true Hosts: Enabled: true - PastePlain: - Enabled: true RegistryPreview: Enabled: true diff --git a/src/gpo/assets/PowerToys.admx b/src/gpo/assets/PowerToys.admx index fef8e1021d8..c54809c35e5 100644 --- a/src/gpo/assets/PowerToys.admx +++ b/src/gpo/assets/PowerToys.admx @@ -1,11 +1,11 @@ - + - + @@ -17,20 +17,21 @@ + - - + + - - + + - - + + @@ -40,7 +41,17 @@ - + + + + + + + + + + + @@ -311,9 +322,9 @@ - + - + @@ -321,9 +332,9 @@ - + - + @@ -331,7 +342,7 @@ - + @@ -341,7 +352,7 @@ - + @@ -351,9 +362,9 @@ - + - + @@ -361,17 +372,7 @@ - - - - - - - - - - - + @@ -411,7 +412,7 @@ - + @@ -421,7 +422,7 @@ - + @@ -431,7 +432,7 @@ - + @@ -441,7 +442,7 @@ - + @@ -451,7 +452,7 @@ - + @@ -471,7 +472,7 @@ - + @@ -481,12 +482,12 @@ - + - - - + + + diff --git a/src/gpo/assets/en-US/PowerToys.adml b/src/gpo/assets/en-US/PowerToys.adml index 9850a68a1c3..e917fc11756 100644 --- a/src/gpo/assets/en-US/PowerToys.adml +++ b/src/gpo/assets/en-US/PowerToys.adml @@ -1,14 +1,14 @@ - + PowerToys PowerToys Microsoft PowerToys - Installer and Updates - PowerToys Run + Installer and Updates + PowerToys Run PowerToys version 0.64.0 or later PowerToys version 0.68.0 or later @@ -19,8 +19,9 @@ PowerToys version 0.76.0 or later PowerToys version 0.77.0 or later PowerToys version 0.78.0 or later + PowerToys version 0.81.0 or later - This policy configures the enabled state for all PowerToys utilities. + This policy configures the enabled state for all PowerToys utilities. If you enable this setting, all utilities will be always enabled and the user won't be able to disable it. @@ -31,7 +32,7 @@ If you don't configure this setting, users are able to enable or disable the uti The individual enabled state policies for the utilities will override this policy. - This policy configures the enabled state for a PowerToys utility. + This policy configures the enabled state for a PowerToys utility. If you enable this setting, the utility will be always enabled and the user won't be able to disable it. @@ -119,6 +120,7 @@ You can set the enabled state for all plugins not configured by this policy usin Note: Changes require a restart of PowerToys Run. Configure global utility enabled state + Advanced Paste: Configure enabled state Always On Top: Configure enabled state Awake: Configure enabled state Color Picker: Configure enabled state @@ -144,12 +146,11 @@ Note: Changes require a restart of PowerToys Run. Mouse Jump: Configure enabled state Mouse Pointer Crosshairs: Configure enabled state Mouse Without Borders: Configure enabled state - Paste as Plain Text: Configure enabled state Peek: Configure enabled state Power Rename: Configure enabled state PowerToys Run: Configure enabled state Quick Accent: Configure enabled state - Registry Preview: Configure enabled state + Registry Preview: Configure enabled state Screen Ruler: Configure enabled state Shortcut Guide: Configure enabled state Text Extractor: Configure enabled state @@ -165,13 +166,13 @@ Note: Changes require a restart of PowerToys Run. QOI file preview: Configure enabled state QOI file thumbnail: Configure enabled state - - - + + + List of managed plugins: - - + + diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj new file mode 100644 index 00000000000..43aa3999aae --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj @@ -0,0 +1,118 @@ + + + + + WinExe + net8.0-windows10.0.20348.0 + 10.0.19041.0 + 10.0.19041.0 + ..\..\..\..\$(Platform)\$(Configuration)\WinUI3Apps + true + Assets\AdvancedPaste\AdvancedPaste.ico + app.manifest + win-x64;win-arm64 + true + false + false + true + None + true + true + PowerToys.AdvancedPaste + PowerToys AdvancedPaste + AdvancedPaste + true + true + + PowerToys.AdvancedPaste.pri + DISABLE_XAML_GENERATED_MAIN,TRACE + + + + + win-x64 + + + win-arm64 + + + + + PowerToys.GPOWrapper + $(OutDir) + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VSTHRD002;VSTHRD110;VSTHRD100;VSTHRD200;VSTHRD101 + + + + + + + + + + + + + + + + + + true + + + + MSBuild:Compile + + + diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml new file mode 100644 index 00000000000..8f9c215823f --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs new file mode 100644 index 00000000000..25f83afb125 --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs @@ -0,0 +1,169 @@ +// 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.Tasks; +using AdvancedPaste.Helpers; +using AdvancedPaste.ViewModels; +using ManagedCommon; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml; +using Windows.ApplicationModel.DataTransfer; +using Windows.Graphics; +using WinUIEx; +using static AdvancedPaste.Helpers.NativeMethods; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. +namespace AdvancedPaste +{ + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + public partial class App : Application, IDisposable + { + public IHost Host { get; private set; } + + private MainWindow window; + + private nint windowHwnd; + + private OptionsViewModel viewModel; + + private bool disposedValue; + + /// + /// Initializes a new instance of the class. + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + this.InitializeComponent(); + + Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder().UseContentRoot(AppContext.BaseDirectory).ConfigureServices((context, services) => + { + services.AddSingleton(); + }).Build(); + + viewModel = GetService(); + + UnhandledException += App_UnhandledException; + } + + public MainWindow GetMainWindow() + { + return window; + } + + public static T GetService() + where T : class + { + if ((App.Current as App)!.Host.Services.GetService(typeof(T)) is not T service) + { + throw new ArgumentException($"{typeof(T)} needs to be registered in ConfigureServices within App.xaml.cs."); + } + + return service; + } + + /// + /// Invoked when the application is launched. + /// + /// Details about the launch request and process. + protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) + { + var cmdArgs = Environment.GetCommandLineArgs(); + if (cmdArgs?.Length > 1) + { + if (int.TryParse(cmdArgs[1], out int powerToysRunnerPid)) + { + RunnerHelper.WaitForPowerToysRunner(powerToysRunnerPid, () => + { + Environment.Exit(0); + }); + } + } + + NativeEventWaiter.WaitForEventLoop(interop.Constants.ShowAdvancedPasteSharedEvent(), OnAdvancedPasteHotkey); + NativeEventWaiter.WaitForEventLoop(interop.Constants.AdvancedPasteMarkdownEvent(), OnAdvancedPasteMarkdownHotkey); + NativeEventWaiter.WaitForEventLoop(interop.Constants.AdvancedPasteJsonEvent(), OnAdvancedPasteJsonHotkey); + } + + private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) + { + Logger.LogError("Unhandled exception", e.Exception); + } + + private void OnAdvancedPasteJsonHotkey() + { + viewModel.GetClipboardData(); + viewModel.ToJsonFunction(true); + } + + private void OnAdvancedPasteMarkdownHotkey() + { + viewModel.GetClipboardData(); + viewModel.ToMarkdownFunction(true); + } + + private void OnAdvancedPasteHotkey() + { + viewModel.OnShow(); + + if (window is null) + { + window = new MainWindow(); + windowHwnd = window.GetWindowHandle(); + + MoveWindowToActiveMonitor(); + + window.Activate(); + } + else + { + MoveWindowToActiveMonitor(); + + Windows.Win32.PInvoke.ShowWindow((Windows.Win32.Foundation.HWND)windowHwnd, Windows.Win32.UI.WindowsAndMessaging.SHOW_WINDOW_CMD.SW_SHOW); + WindowHelpers.BringToForeground(windowHwnd); + } + + window.SetFocus(); + } + + private void MoveWindowToActiveMonitor() + { + if (GetCursorPos(out PointInter cursorPosition)) + { + DisplayArea displayArea = DisplayArea.GetFromPoint(new PointInt32(cursorPosition.X, cursorPosition.Y), DisplayAreaFallback.Nearest); + + var x = displayArea.WorkArea.X + (displayArea.WorkArea.Width / 2) - (window.Width / 2); + var y = displayArea.WorkArea.Y + (displayArea.WorkArea.Height / 2) - (window.Height / 2); + + window.MoveAndResize(x, y, window.Width, window.Height); + } + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + window.Dispose(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/AnimatedContentControl/AnimatedBorderBrush.cs b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/AnimatedContentControl/AnimatedBorderBrush.cs new file mode 100644 index 00000000000..44aea497a5e --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/AnimatedContentControl/AnimatedBorderBrush.cs @@ -0,0 +1,165 @@ +// 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.Numerics; +using Microsoft.UI.Composition; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Media; +using Windows.Storage; +using Windows.Storage.Streams; + +namespace AdvancedPaste.Controls +{ + public partial class AnimatedBorderBrush : XamlCompositionBrushBase + { + public static readonly DependencyProperty IsLoadingProperty = DependencyProperty.Register( + nameof(IsLoading), + typeof(bool), + typeof(AnimatedBorderBrush), + new PropertyMetadata(defaultValue: false, OnIsLoadingChanged)); + + public bool IsLoading + { + get => (bool)GetValue(IsLoadingProperty); + set => SetValue(IsLoadingProperty, value); + } + + public static readonly DependencyProperty DurationProperty = DependencyProperty.Register( + nameof(Duration), + typeof(int), + typeof(AnimatedBorderBrush), + new PropertyMetadata(defaultValue: 400, OnDurationChanged)); + + public int Duration + { + get => (int)GetValue(DurationProperty); + set => SetValue(DurationProperty, value); + } + + private static void OnIsLoadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs newValue) + { + var selectionRectangle = (AnimatedBorderBrush)d; + selectionRectangle.IsLoadingChanged(); + } + + private static void OnDurationChanged(DependencyObject d, DependencyPropertyChangedEventArgs newValue) + { + var selectionRectangle = (AnimatedBorderBrush)d; + selectionRectangle.DurationChanged(); + } + + private Compositor compositor; + private bool isConnected; + private double centerWidth; + private double centerHeight; + private CompositionAnimationGroup animationGroup; + private CompositionSurfaceBrush gradientBrush; + + public AnimatedBorderBrush() + { + compositor = CompositionTarget.GetCompositorForCurrentThread(); + } + + private void DurationChanged() + { + if (!isConnected) + { + return; + } + + if (IsLoading) + { + PlayAnimation(true); + } + else + { + animationGroup = null; + } + } + + protected override void OnConnected() + { + isConnected = true; + IsLoadingChanged(); + } + + protected override void OnDisconnected() + { + isConnected = false; + CompositionBrush = null; + gradientBrush = null; + } + + private void IsLoadingChanged() + { + if (!isConnected) + { + return; + } + + if (IsLoading) + { + if (CompositionBrush == null) + { + var brush = compositor.CreateSurfaceBrush(); + var loadedSurface = LoadedImageSurface.StartLoadFromUri(new Uri("ms-appx:///Assets/AdvancedPaste/Gradient.png")); + brush.Surface = loadedSurface; + brush.HorizontalAlignmentRatio = 0.5f; + brush.VerticalAlignmentRatio = 0.5f; + brush.Stretch = CompositionStretch.UniformToFill; + brush.BitmapInterpolationMode = CompositionBitmapInterpolationMode.MagLinearMinLinearMipLinear; + brush.Scale = new Vector2(1.4f, 1.4f); + CompositionBrush = brush; + gradientBrush = brush; + gradientBrush.CenterPoint = new Vector2((float)centerWidth / 2, (float)centerHeight / 2); + } + + PlayAnimation(false); + } + else + { + if (animationGroup != null && gradientBrush != null) + { + gradientBrush.StopAnimationGroup(animationGroup); + } + } + } + + public void UpdateSize(double width, double height) + { + centerWidth = width; + centerHeight = height; + + if (gradientBrush != null) + { + gradientBrush.CenterPoint = new Vector2((float)width / 2, (float)height / 2); + } + } + + private void PlayAnimation(bool reset) + { + if (reset || animationGroup == null) + { + InitializeAnimation(); + } + + gradientBrush.StopAnimationGroup(animationGroup); + gradientBrush.StartAnimationGroup(animationGroup); + } + + private void InitializeAnimation() + { + animationGroup = compositor.CreateAnimationGroup(); + var animation = compositor.CreateScalarKeyFrameAnimation(); + animation.Duration = TimeSpan.FromMilliseconds(Duration); + animation.IterationBehavior = AnimationIterationBehavior.Forever; + var easing = compositor.CreateLinearEasingFunction(); + animation.InsertKeyFrame(0, 0, easing); + animation.InsertKeyFrame(1, 360, easing); + animation.Target = "RotationAngleInDegrees"; + animationGroup.Add(animation); + } + } +} diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/AnimatedContentControl/AnimatedBorderBrush.xaml b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/AnimatedContentControl/AnimatedBorderBrush.xaml new file mode 100644 index 00000000000..b73ce13a307 --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/AnimatedContentControl/AnimatedBorderBrush.xaml @@ -0,0 +1,7 @@ + diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/AnimatedContentControl/AnimatedContentControl.cs b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/AnimatedContentControl/AnimatedContentControl.cs new file mode 100644 index 00000000000..eda04a5d537 --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/AnimatedContentControl/AnimatedContentControl.cs @@ -0,0 +1,91 @@ +// 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.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace AdvancedPaste.Controls +{ + [TemplatePart(Name = LoadingGrid, Type = typeof(Grid))] + [TemplatePart(Name = LoadingBrush, Type = typeof(AnimatedBorderBrush))] + public class AnimatedContentControl : ContentControl + { + internal const string LoadingGrid = "PART_LoadingGrid"; + internal const string LoadingBrush = "PART_LoadingBrush"; + + public string Text + { + get => (string)GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + public static readonly DependencyProperty TextProperty = DependencyProperty.Register( + nameof(Text), + typeof(string), + typeof(AnimatedContentControl), + new PropertyMetadata(defaultValue: null)); + + public bool IsLoading + { + get => (bool)GetValue(IsLoadingProperty); + set => SetValue(IsLoadingProperty, value); + } + + public static readonly DependencyProperty IsLoadingProperty = DependencyProperty.Register( + nameof(IsLoading), + typeof(bool), + typeof(AnimatedContentControl), + new PropertyMetadata(defaultValue: false, (d, e) => ((AnimatedContentControl)d).OnIsLoadingPropertyChanged((bool)e.OldValue, (bool)e.NewValue))); + + public AnimatedContentControl() + { + this.DefaultStyleKey = typeof(AnimatedContentControl); + } + + protected override void OnApplyTemplate() + { + this.SizeChanged -= AICard_SizeChanged; + OnIsLoadingChanged(); + UpdateSize(Width, Height); + this.SizeChanged += AICard_SizeChanged; + } + + private void AICard_SizeChanged(object sender, SizeChangedEventArgs e) + { + if (GetTemplateChild(LoadingBrush) is AnimatedBorderBrush loadingBrush) + { + UpdateSize(e.NewSize.Width, e.NewSize.Height); + } + } + + private void UpdateSize(double width, double height) + { + if (GetTemplateChild(LoadingBrush) is AnimatedBorderBrush loadingBrush) + { + loadingBrush.UpdateSize(width, height); + } + } + + protected virtual void OnIsLoadingPropertyChanged(bool oldValue, bool newValue) + { + OnIsLoadingChanged(); + } + + private void OnIsLoadingChanged() + { + if (GetTemplateChild(LoadingBrush) is AnimatedBorderBrush loadingBrush) + { + UpdateSize(ActualWidth, ActualHeight); + + loadingBrush.IsLoading = IsLoading; + } + + if (GetTemplateChild(LoadingGrid) is Grid loadingGrid) + { + loadingGrid.Visibility = IsLoading ? Visibility.Visible : Visibility.Collapsed; + UpdateSize(ActualWidth, ActualHeight); + } + } + } +} diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/AnimatedContentControl/AnimatedContentControl.xaml b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/AnimatedContentControl/AnimatedContentControl.xaml new file mode 100644 index 00000000000..00d672c6f0a --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/AnimatedContentControl/AnimatedContentControl.xaml @@ -0,0 +1,58 @@ + + + + diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/PromptBox.xaml b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/PromptBox.xaml new file mode 100644 index 00000000000..e139ac4c31a --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/PromptBox.xaml @@ -0,0 +1,662 @@ + + + + + + #65C8F2 + + + + + + + + #005FB8 + + + + + + + + #48B1E9 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/PromptBox.xaml.cs b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/PromptBox.xaml.cs new file mode 100644 index 00000000000..691c7257d5c --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/PromptBox.xaml.cs @@ -0,0 +1,178 @@ +// 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.Net; +using System.Threading.Tasks; +using AdvancedPaste.Helpers; +using AdvancedPaste.Settings; +using AdvancedPaste.ViewModels; +using CommunityToolkit.Mvvm.Input; +using ManagedCommon; +using Microsoft.PowerToys.Telemetry; +using Microsoft.UI.Dispatching; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace AdvancedPaste.Controls +{ + public sealed partial class PromptBox : Microsoft.UI.Xaml.Controls.UserControl + { + private readonly DispatcherQueue _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + + private UserSettings _userSettings; + + public static readonly DependencyProperty PromptProperty = DependencyProperty.Register( + nameof(Prompt), + typeof(string), + typeof(PromptBox), + new PropertyMetadata(defaultValue: string.Empty)); + + public OptionsViewModel ViewModel { get; private set; } + + public string Prompt + { + get => (string)GetValue(PromptProperty); + set => SetValue(PromptProperty, value); + } + + public static readonly DependencyProperty PlaceholderTextProperty = DependencyProperty.Register( + nameof(PlaceholderText), + typeof(string), + typeof(PromptBox), + new PropertyMetadata(defaultValue: string.Empty)); + + public string PlaceholderText + { + get => (string)GetValue(PlaceholderTextProperty); + set => SetValue(PlaceholderTextProperty, value); + } + + public static readonly DependencyProperty FooterProperty = DependencyProperty.Register( + nameof(Footer), + typeof(object), + typeof(PromptBox), + new PropertyMetadata(defaultValue: null)); + + public object Footer + { + get => (object)GetValue(FooterProperty); + set => SetValue(FooterProperty, value); + } + + public PromptBox() + { + this.InitializeComponent(); + + _userSettings = new UserSettings(); + + ViewModel = App.GetService(); + } + + private void Grid_Loaded(object sender, RoutedEventArgs e) + { + InputTxtBox.Focus(FocusState.Programmatic); + } + + [RelayCommand] + private void GenerateCustom() + { + Logger.LogTrace(); + + PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteGenerateCustomFormatEvent()); + + VisualStateManager.GoToState(this, "LoadingState", true); + string inputInstructions = InputTxtBox.Text; + ViewModel.SaveQuery(inputInstructions); + + var customFormatTask = ViewModel.GenerateCustomFunction(inputInstructions); + + customFormatTask.ContinueWith( + t => + { + _dispatcherQueue.TryEnqueue(() => + { + ViewModel.CustomFormatResult = t.Result; + + if (ViewModel.ApiRequestStatus == (int)HttpStatusCode.OK) + { + VisualStateManager.GoToState(this, "DefaultState", true); + if (_userSettings.ShowCustomPreview) + { + PreviewGrid.Width = InputTxtBox.ActualWidth; + PreviewFlyout.ShowAt(InputTxtBox); + } + else + { + ViewModel.PasteCustom(); + InputTxtBox.Text = string.Empty; + } + } + else + { + VisualStateManager.GoToState(this, "ErrorState", true); + } + }); + }, + TaskScheduler.Default); + } + + [RelayCommand] + private void Recall() + { + Logger.LogTrace(); + + InputTxtBox.IsEnabled = true; + + var lastQuery = ViewModel.RecallPreviousCustomQuery(); + if (lastQuery != null) + { + InputTxtBox.Text = lastQuery.Query; + } + + ClipboardHelper.SetClipboardTextContent(lastQuery.ClipboardData); + } + + private void InputTxtBox_TextChanging(Microsoft.UI.Xaml.Controls.TextBox sender, TextBoxTextChangingEventArgs args) + { + SendBtn.Visibility = InputTxtBox.Text.Length > 0 ? Visibility.Visible : Visibility.Collapsed; + } + + private void InputTxtBox_KeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e) + { + if (e.Key == Windows.System.VirtualKey.Enter && InputTxtBox.Text.Length > 0) + { + GenerateCustom(); + } + } + + private void PreviewPasteBtn_Click(object sender, RoutedEventArgs e) + { + ViewModel.PasteCustom(); + InputTxtBox.Text = string.Empty; + } + + private void ThumbUpDown_Click(object sender, RoutedEventArgs e) + { + if (sender is Button btn) + { + bool result; + if (bool.TryParse(btn.CommandParameter as string, out result)) + { + PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteCustomFormatOutputThumbUpDownEvent(result)); + } + } + } + + private void PreviewFlyout_Opened(object sender, object e) + { + PreviewPasteBtn.Focus(FocusState.Programmatic); + } + + internal void IsLoading(bool loading) + { + Loader.IsLoading = loading; + } + } +} diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Converters/ListViewIndexConverter.cs b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Converters/ListViewIndexConverter.cs new file mode 100644 index 00000000000..a50ec1fc0ef --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Converters/ListViewIndexConverter.cs @@ -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 Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Data; +using Microsoft.UI.Xaml.Media; + +namespace AdvancedPaste.Converters +{ + public sealed class ListViewIndexConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + var presenter = value as ListViewItemPresenter; + var item = VisualTreeHelper.GetParent(presenter) as ListViewItem; + + var listView = ItemsControl.ItemsControlFromItemContainer(item); + int index = listView.IndexFromContainer(item) + 1; +#pragma warning disable CA1305 // Specify IFormatProvider + return index.ToString(); +#pragma warning restore CA1305 // Specify IFormatProvider + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/MainWindow.xaml b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/MainWindow.xaml new file mode 100644 index 00000000000..8aa111219cb --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/MainWindow.xaml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/MainWindow.xaml.cs b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/MainWindow.xaml.cs new file mode 100644 index 00000000000..49754534536 --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/MainWindow.xaml.cs @@ -0,0 +1,99 @@ +// 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.CompilerServices; +using AdvancedPaste.Helpers; +using ManagedCommon; +using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml; +using Windows.Graphics; +using WinUIEx; +using WinUIEx.Messaging; +using static AdvancedPaste.Helpers.NativeMethods; + +namespace AdvancedPaste +{ + public sealed partial class MainWindow : WindowEx, IDisposable + { + private WindowMessageMonitor _msgMonitor; + + private bool _disposedValue; + + public MainWindow() + { + this.InitializeComponent(); + + AppWindow.SetIcon("Assets/AdvancedPaste/AdvancedPaste.ico"); + this.ExtendsContentIntoTitleBar = true; + this.SetTitleBar(titleBar); + + var loader = ResourceLoaderInstance.ResourceLoader; + Title = loader.GetString("WindowTitle"); + + _msgMonitor = new WindowMessageMonitor(this); + _msgMonitor.WindowMessageReceived += (_, e) => + { + const int WM_NCLBUTTONDBLCLK = 0x00A3; + if (e.Message.MessageId == WM_NCLBUTTONDBLCLK) + { + // Disable double click on title bar to maximize window + e.Result = 0; + e.Handled = true; + } + }; + + WindowHelpers.BringToForeground(this.GetWindowHandle()); + } + + private void Dispose(bool disposing) + { + if (!_disposedValue) + { + _msgMonitor?.Dispose(); + + _disposedValue = true; + } + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + private void WindowEx_Closed(object sender, Microsoft.UI.Xaml.WindowEventArgs args) + { + Windows.Win32.PInvoke.ShowWindow((Windows.Win32.Foundation.HWND)this.GetWindowHandle(), Windows.Win32.UI.WindowsAndMessaging.SHOW_WINDOW_CMD.SW_HIDE); + + args.Handled = true; + } + + public void SetFocus() + { + MainPage.CustomFormatTextBox.InputTxtBox.Focus(FocusState.Programmatic); + } + + public void ClearInputText() + { + MainPage.CustomFormatTextBox.InputTxtBox.Text = string.Empty; + } + + internal void StartLoading() + { + MainPage.CustomFormatTextBox.IsLoading(true); + } + + internal void FinishLoading(bool success) + { + MainPage.CustomFormatTextBox.IsLoading(false); + + if (success) + { + VisualStateManager.GoToState(MainPage.CustomFormatTextBox, "DefaultState", true); + } + } + } +} diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Pages/MainPage.xaml b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Pages/MainPage.xaml new file mode 100644 index 00000000000..2979155c1fc --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Pages/MainPage.xaml @@ -0,0 +1,274 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Pages/MainPage.xaml.cs b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Pages/MainPage.xaml.cs new file mode 100644 index 00000000000..1aa49c1014a --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Pages/MainPage.xaml.cs @@ -0,0 +1,241 @@ +// 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.Threading; +using System.Threading.Tasks; +using AdvancedPaste.Helpers; +using AdvancedPaste.Models; +using AdvancedPaste.ViewModels; +using ManagedCommon; +using Microsoft.PowerToys.Telemetry; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Media.Imaging; +using Windows.ApplicationModel.DataTransfer; +using Windows.Storage.Streams; +using Windows.System; + +namespace AdvancedPaste.Pages +{ + public sealed partial class MainPage : Page + { + private readonly ObservableCollection clipboardHistory; + private readonly ObservableCollection pasteFormats; + private readonly Microsoft.UI.Dispatching.DispatcherQueue _dispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); + + public OptionsViewModel ViewModel { get; private set; } + + public MainPage() + { + this.InitializeComponent(); + + pasteFormats = + [ + new PasteFormat { Icon = new FontIcon() { Glyph = "\uE8E9" }, Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsPlainText"), Format = PasteFormats.PlainText }, + new PasteFormat { Icon = new FontIcon() { Glyph = "\ue8a5" }, Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsMarkdown"), Format = PasteFormats.Markdown }, + new PasteFormat { Icon = new FontIcon() { Glyph = "\uE943" }, Name = ResourceLoaderInstance.ResourceLoader.GetString("PasteAsJson"), Format = PasteFormats.Json }, + ]; + + ViewModel = App.GetService(); + + clipboardHistory = new ObservableCollection(); + + LoadClipboardHistoryEvent(null, null); + Clipboard.HistoryChanged += LoadClipboardHistoryEvent; + } + + private void LoadClipboardHistoryEvent(object sender, object e) + { + Task.Run(() => + { + LoadClipboardHistoryAsync(); + }); + } + + public async void LoadClipboardHistoryAsync() + { + try + { + Logger.LogTrace(); + + List items = new(); + + if (Clipboard.IsHistoryEnabled()) + { + var historyItems = await Clipboard.GetHistoryItemsAsync(); + if (historyItems.Status == ClipboardHistoryItemsResultStatus.Success) + { + foreach (var item in historyItems.Items) + { + if (item.Content.Contains(StandardDataFormats.Text)) + { + string text = await item.Content.GetTextAsync(); + items.Add(new ClipboardItem { Content = text, Item = item }); + } + else if (item.Content.Contains(StandardDataFormats.Bitmap)) + { + items.Add(new ClipboardItem { Item = item }); + } + } + } + } + + _dispatcherQueue.TryEnqueue(async () => + { + clipboardHistory.Clear(); + + foreach (var item in items) + { + if (item.Item.Content.Contains(StandardDataFormats.Bitmap)) + { + IRandomAccessStreamReference imageReceived = null; + imageReceived = await item.Item.Content.GetBitmapAsync(); + if (imageReceived != null) + { + using (var imageStream = await imageReceived.OpenReadAsync()) + { + var bitmapImage = new BitmapImage(); + bitmapImage.SetSource(imageStream); + item.Image = bitmapImage; + } + } + } + + clipboardHistory.Add(item); + } + }); + } + catch (Exception ex) + { + Logger.LogError("Loading clipboard history failed", ex); + } + } + + private void ClipboardHistoryItemDeleteButton_Click(object sender, RoutedEventArgs e) + { + Logger.LogTrace(); + + if (sender is MenuFlyoutItem btn) + { + ClipboardItem item = btn.CommandParameter as ClipboardItem; + Clipboard.DeleteItemFromHistory(item.Item); + clipboardHistory.Remove(item); + + PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteClipboardItemDeletedEvent()); + } + } + + private void PasteAsPlain() + { + ViewModel.ToPlainTextFunction(); + } + + private void PasteAsMarkdown() + { + ViewModel.ToMarkdownFunction(); + } + + private void PasteAsJson() + { + ViewModel.ToJsonFunction(); + } + + private void PasteOptionsListView_ItemClick(object sender, ItemClickEventArgs e) + { + if (e.ClickedItem is PasteFormat format) + { + switch (format.Format) + { + case PasteFormats.PlainText: + { + PasteAsPlain(); + PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteFormatClickedEvent(PasteFormats.PlainText)); + break; + } + + case PasteFormats.Markdown: + { + PasteAsMarkdown(); + PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteFormatClickedEvent(PasteFormats.Markdown)); + break; + } + + case PasteFormats.Json: + { + PasteAsJson(); + PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteFormatClickedEvent(PasteFormats.Json)); + break; + } + } + } + } + + private void KeyboardAccelerator_Invoked(Microsoft.UI.Xaml.Input.KeyboardAccelerator sender, Microsoft.UI.Xaml.Input.KeyboardAcceleratorInvokedEventArgs args) + { + Logger.LogTrace(); + + switch (sender.Key) + { + case VirtualKey.Escape: + { + (App.Current as App).GetMainWindow().Close(); + break; + } + + case VirtualKey.Number1: + { + PasteAsPlain(); + PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteInAppKeyboardShortcutEvent(PasteFormats.PlainText)); + break; + } + + case VirtualKey.Number2: + { + PasteAsMarkdown(); + PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteInAppKeyboardShortcutEvent(PasteFormats.Markdown)); + break; + } + + case VirtualKey.Number3: + { + PasteAsJson(); + PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteInAppKeyboardShortcutEvent(PasteFormats.Json)); + break; + } + + default: + break; + } + } + + private void Page_KeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e) + { + if (e.Key == VirtualKey.Escape) + { + (App.Current as App).GetMainWindow().Close(); + } + } + + private async void ClipboardHistory_ItemClick(object sender, ItemClickEventArgs e) + { + var item = e.ClickedItem as ClipboardItem; + if (item is not null) + { + if (!string.IsNullOrEmpty(item.Content)) + { + ClipboardHelper.SetClipboardTextContent(item.Content); + } + else if (item.Image is not null) + { + RandomAccessStreamReference image = null; + image = await item.Item.Content.GetBitmapAsync(); + ClipboardHelper.SetClipboardImageContent(image); + } + } + } + } +} diff --git a/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Themes/Generic.xaml b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Themes/Generic.xaml new file mode 100644 index 00000000000..bc1d711b26f --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Themes/Generic.xaml @@ -0,0 +1,9 @@ + + + + + + diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/AIIcon.png b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/AIIcon.png new file mode 100644 index 00000000000..8dd29e186d2 Binary files /dev/null and b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/AIIcon.png differ diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/AdvancedPaste.ico b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/AdvancedPaste.ico new file mode 100644 index 00000000000..a9f170a8bd9 Binary files /dev/null and b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/AdvancedPaste.ico differ diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/AdvancedPaste.png b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/AdvancedPaste.png new file mode 100644 index 00000000000..8f6b9085202 Binary files /dev/null and b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/AdvancedPaste.png differ diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/Gradient.png b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/Gradient.png new file mode 100644 index 00000000000..73621edfc0f Binary files /dev/null and b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/Gradient.png differ diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/LockScreenLogo.scale-200.png b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/LockScreenLogo.scale-200.png new file mode 100644 index 00000000000..7440f0d4bf7 Binary files /dev/null and b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/LockScreenLogo.scale-200.png differ diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/SplashScreen.scale-200.png b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/SplashScreen.scale-200.png new file mode 100644 index 00000000000..32f486a8679 Binary files /dev/null and b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/SplashScreen.scale-200.png differ diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/Square150x150Logo.scale-200.png b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/Square150x150Logo.scale-200.png new file mode 100644 index 00000000000..53ee3777ea2 Binary files /dev/null and b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/Square150x150Logo.scale-200.png differ diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/Square44x44Logo.scale-200.png b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/Square44x44Logo.scale-200.png new file mode 100644 index 00000000000..f713bba67f5 Binary files /dev/null and b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/Square44x44Logo.scale-200.png differ diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/Square44x44Logo.targetsize-24_altform-unplated.png b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 00000000000..dc9f5bea0c3 Binary files /dev/null and b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/StoreLogo.png b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/StoreLogo.png new file mode 100644 index 00000000000..a4586f26bdf Binary files /dev/null and b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/StoreLogo.png differ diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/Wide310x150Logo.scale-200.png b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/Wide310x150Logo.scale-200.png new file mode 100644 index 00000000000..8b4a5d0dd5f Binary files /dev/null and b/src/modules/AdvancedPaste/AdvancedPaste/Assets/AdvancedPaste/Wide310x150Logo.scale-200.png differ diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/AICompletionsHelper.cs b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/AICompletionsHelper.cs new file mode 100644 index 00000000000..11c69ff120a --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/AICompletionsHelper.cs @@ -0,0 +1,133 @@ +// 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.Globalization; +using System.IO; +using System.Net; +using Azure; +using Azure.AI.OpenAI; +using ManagedCommon; +using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.PowerToys.Telemetry; +using Windows.Security.Credentials; + +namespace AdvancedPaste.Helpers +{ + public class AICompletionsHelper + { + // Return Response and Status code from the request. + public struct AICompletionsResponse + { + public AICompletionsResponse(string response, int apiRequestStatus) + { + Response = response; + ApiRequestStatus = apiRequestStatus; + } + + public string Response { get; } + + public int ApiRequestStatus { get; } + } + + private string _openAIKey; + + public bool IsAIEnabled => !string.IsNullOrEmpty(this._openAIKey); + + public AICompletionsHelper() + { + this._openAIKey = LoadOpenAIKey(); + } + + public void SetOpenAIKey(string openAIKey) + { + this._openAIKey = openAIKey; + } + + public string GetKey() + { + return _openAIKey; + } + + public static string LoadOpenAIKey() + { + PasswordVault vault = new PasswordVault(); + + try + { + PasswordCredential cred = vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey"); + if (cred is not null) + { + return cred.Password.ToString(); + } + } + catch (Exception) + { + } + + return string.Empty; + } + + public string GetAICompletion(string systemInstructions, string userMessage) + { + OpenAIClient azureAIClient = new OpenAIClient(_openAIKey); + + var response = azureAIClient.GetCompletions( + new CompletionsOptions() + { + DeploymentName = "gpt-3.5-turbo-instruct", + Prompts = + { + systemInstructions + "\n\n" + userMessage, + }, + Temperature = 0.01F, + MaxTokens = 2000, + }); + + if (response.Value.Choices[0].FinishReason == "length") + { + Console.WriteLine("Cut off due to length constraints"); + } + + return response.Value.Choices[0].Text; + } + + public AICompletionsResponse AIFormatString(string inputInstructions, string inputString) + { + string systemInstructions = $@"You are tasked with reformatting user's clipboard data. Use the user's instructions, and the content of their clipboard below to edit their clipboard content as they have requested it. + +Do not output anything else besides the reformatted clipboard content."; + + string userMessage = $@"User instructions: +{inputInstructions} + +Clipboard Content: +{inputString} + +Output: +"; + + string aiResponse = null; + int apiRequestStatus = (int)HttpStatusCode.OK; + try + { + aiResponse = this.GetAICompletion(systemInstructions, userMessage); + } + catch (Azure.RequestFailedException error) + { + Logger.LogError("GetAICompletion failed", error); + PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteGenerateCustomErrorEvent(error.Message)); + apiRequestStatus = error.Status; + } + catch (Exception error) + { + Logger.LogError("GetAICompletion failed", error); + PowerToysTelemetry.Log.WriteEvent(new Telemetry.AdvancedPasteGenerateCustomErrorEvent(error.Message)); + apiRequestStatus = -1; + } + + return new AICompletionsResponse(aiResponse, apiRequestStatus); + } + } +} diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/ClipboardHelper.cs b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/ClipboardHelper.cs new file mode 100644 index 00000000000..4cfa524ad33 --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/ClipboardHelper.cs @@ -0,0 +1,139 @@ +// 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.Threading.Tasks; +using ManagedCommon; +using Microsoft.UI.Xaml.Media.Imaging; +using Windows.ApplicationModel.DataTransfer; +using Windows.Storage.Streams; +using Windows.System; + +namespace AdvancedPaste.Helpers +{ + internal static class ClipboardHelper + { + internal static void SetClipboardTextContent(string text) + { + Logger.LogTrace(); + + if (!string.IsNullOrEmpty(text)) + { + DataPackage output = new(); + output.SetText(text); + Clipboard.SetContentWithOptions(output, null); + + // TODO(stefan): For some reason Flush() fails from time to time when directly activated via hotkey. + // Calling inside a loop makes it work. + bool flushed = false; + for (int i = 0; i < 5; i++) + { + if (flushed) + { + break; + } + + try + { + Task.Run(() => + { + Clipboard.Flush(); + }).Wait(); + + flushed = true; + } + catch (Exception ex) + { + Logger.LogError("Clipboard.Flush() failed", ex); + } + } + } + } + + internal static void SetClipboardImageContent(RandomAccessStreamReference image) + { + Logger.LogTrace(); + + if (image is not null) + { + DataPackage output = new(); + output.SetBitmap(image); + Clipboard.SetContentWithOptions(output, null); + + // TODO(stefan): For some reason Flush() fails from time to time when directly activated via hotkey. + // Calling inside a loop makes it work. + bool flushed = false; + for (int i = 0; i < 5; i++) + { + if (flushed) + { + break; + } + + try + { + Task.Run(() => + { + Clipboard.Flush(); + }).Wait(); + + flushed = true; + } + catch (Exception ex) + { + Logger.LogError("Clipboard.Flush() failed", ex); + } + } + } + } + + // Function to send a single key event + private static void SendSingleKeyboardInput(short keyCode, uint keyStatus) + { + UIntPtr ignoreKeyEventFlag = (UIntPtr)0x5555; + + NativeMethods.INPUT inputShift = new NativeMethods.INPUT + { + type = NativeMethods.INPUTTYPE.INPUT_KEYBOARD, + data = new NativeMethods.InputUnion + { + ki = new NativeMethods.KEYBDINPUT + { + wVk = keyCode, + dwFlags = keyStatus, + + // Any keyevent with the extraInfo set to this value will be ignored by the keyboard hook and sent to the system instead. + dwExtraInfo = ignoreKeyEventFlag, + }, + }, + }; + + NativeMethods.INPUT[] inputs = new NativeMethods.INPUT[] { inputShift }; + _ = NativeMethods.SendInput(1, inputs, NativeMethods.INPUT.Size); + } + + internal static void SendPasteKeyCombination() + { + Logger.LogTrace(); + + SendSingleKeyboardInput((short)VirtualKey.LeftControl, (uint)NativeMethods.KeyEventF.KeyUp); + SendSingleKeyboardInput((short)VirtualKey.RightControl, (uint)NativeMethods.KeyEventF.KeyUp); + SendSingleKeyboardInput((short)VirtualKey.LeftWindows, (uint)NativeMethods.KeyEventF.KeyUp); + SendSingleKeyboardInput((short)VirtualKey.RightWindows, (uint)NativeMethods.KeyEventF.KeyUp); + SendSingleKeyboardInput((short)VirtualKey.LeftShift, (uint)NativeMethods.KeyEventF.KeyUp); + SendSingleKeyboardInput((short)VirtualKey.RightShift, (uint)NativeMethods.KeyEventF.KeyUp); + SendSingleKeyboardInput((short)VirtualKey.LeftMenu, (uint)NativeMethods.KeyEventF.KeyUp); + SendSingleKeyboardInput((short)VirtualKey.RightMenu, (uint)NativeMethods.KeyEventF.KeyUp); + + // Send Ctrl + V + SendSingleKeyboardInput((short)VirtualKey.Control, (uint)NativeMethods.KeyEventF.KeyDown); + SendSingleKeyboardInput((short)VirtualKey.V, (uint)NativeMethods.KeyEventF.KeyDown); + SendSingleKeyboardInput((short)VirtualKey.V, (uint)NativeMethods.KeyEventF.KeyUp); + SendSingleKeyboardInput((short)VirtualKey.Control, (uint)NativeMethods.KeyEventF.KeyUp); + + Logger.LogInfo("Paste sent"); + } + } +} diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/Constants.cs b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/Constants.cs new file mode 100644 index 00000000000..deeef8551f3 --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/Constants.cs @@ -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 AdvancedPaste.Helpers +{ + internal static class Constants + { + internal static readonly string AdvancedPasteModuleName = "AdvancedPaste"; + internal static readonly string LastQueryJsonFileName = "lastQuery.json"; + } +} diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/IUserSettings.cs b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/IUserSettings.cs new file mode 100644 index 00000000000..e8394c002ec --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/IUserSettings.cs @@ -0,0 +1,13 @@ +// 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 AdvancedPaste.Settings +{ + public interface IUserSettings + { + public bool ShowCustomPreview { get; } + + public bool SendPasteKeyCombination { get; } + } +} diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/JsonHelper.cs b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/JsonHelper.cs new file mode 100644 index 00000000000..e8f1df2ade1 --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/JsonHelper.cs @@ -0,0 +1,72 @@ +// 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.Threading.Tasks; +using System.Xml; +using ManagedCommon; +using Newtonsoft.Json; +using Windows.ApplicationModel.DataTransfer; + +namespace AdvancedPaste.Helpers +{ + internal static class JsonHelper + { + internal static string ToJsonFromXmlOrCsv(DataPackageView clipboardData) + { + Logger.LogTrace(); + + if (clipboardData == null || !clipboardData.Contains(StandardDataFormats.Text)) + { + Logger.LogWarning("Clipboard does not contain text data"); + return string.Empty; + } + +#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits + string text = Task.Run(async () => + { + string plainText = await clipboardData.GetTextAsync() as string; + return plainText; + }).Result; +#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits + + string jsonText = string.Empty; + + // Try convert XML + try + { + XmlDocument doc = new XmlDocument(); + doc.LoadXml(text); + jsonText = JsonConvert.SerializeXmlNode(doc, Newtonsoft.Json.Formatting.Indented); + } + catch (Exception ex) + { + Logger.LogError("Failed parsing input as xml", ex); + } + + // Try convert CSV + try + { + if (string.IsNullOrEmpty(jsonText)) + { + var csv = new List(); + + foreach (var line in text.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)) + { + csv.Add(line.Split(",")); + } + + jsonText = JsonConvert.SerializeObject(csv, Newtonsoft.Json.Formatting.Indented); + } + } + catch (Exception ex) + { + Logger.LogError("Failed parsing input as csv", ex); + } + + return string.IsNullOrEmpty(jsonText) ? text : jsonText; + } + } +} diff --git a/src/modules/AdvancedPaste/AdvancedPaste/Helpers/MarkdownHelper.cs b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/MarkdownHelper.cs new file mode 100644 index 00000000000..e096a419b90 --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPaste/Helpers/MarkdownHelper.cs @@ -0,0 +1,171 @@ +// 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.Linq; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using HtmlAgilityPack; +using ManagedCommon; +using Windows.ApplicationModel.DataTransfer; + +namespace AdvancedPaste.Helpers +{ + internal static class MarkdownHelper + { + public static string ToMarkdown(DataPackageView clipboardData) + { + Logger.LogTrace(); + + if (clipboardData == null) + { + Logger.LogWarning("Clipboard does not contain data"); + + return string.Empty; + } + + string data = string.Empty; + + if (clipboardData.Contains(StandardDataFormats.Html)) + { + data = Task.Run(async () => + { + string data = await clipboardData.GetHtmlFormatAsync() as string; + return data; + }).Result; + } + else if (clipboardData.Contains(StandardDataFormats.Text)) + { + data = Task.Run(async () => + { + string plainText = await clipboardData.GetTextAsync() as string; + return plainText; + }).Result; + } + + if (!string.IsNullOrEmpty(data)) + { + string cleanedHtml = CleanHtml(data); + + return ConvertHtmlToMarkdown(cleanedHtml); + } + + return string.Empty; + } + + public static string PasteAsPlainTextFromClipboard(DataPackageView clipboardData) + { + Logger.LogTrace(); + + if (clipboardData != null) + { + if (!clipboardData.Contains(StandardDataFormats.Text)) + { + Logger.LogWarning("Clipboard does not contain text data"); + + return string.Empty; + } + + return Task.Run(async () => + { + string plainText = await clipboardData.GetTextAsync() as string; + return plainText; + }).Result; + } + + return string.Empty; + } + + private static string CleanHtml(string html) + { + Logger.LogTrace(); + + // Remove the "StartFragment" and "EndFragment" comments + html = Regex.Replace(html, @"|", string.Empty); + + HtmlDocument document = new HtmlDocument(); + document.LoadHtml(html); + + // Remove unwanted HTML elements + RemoveUnwantedElements(document.DocumentNode); + + // Remove inline styles + RemoveInlineStyles(document.DocumentNode); + + // Clean up line breaks and whitespace + CleanUpWhitespace(document.DocumentNode); + + // Serialize the cleaned HTML back to string + using (var writer = new System.IO.StringWriter()) + { + document.Save(writer); + return writer.ToString(); + } + } + + private static void RemoveUnwantedElements(HtmlNode node) + { + Logger.LogTrace(); + + // Remove specific elements by tag name, CSS class, or other attributes + // Example: Remove all