diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index c2e7270be1d..a7551312327 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -721,6 +721,7 @@ HTTRANSPARENT HValue Hvci hwb +HWHEEL HWINEVENTHOOK hwnd HWNDFIRST @@ -893,6 +894,8 @@ LCONTROL LCtrl Ldone ldx +LEFTDOWN +LEFTUP LEFTSCROLLBAR lego len @@ -1041,6 +1044,8 @@ Mgmt mic microsoft Midl +MIDDLEDOWN +MIDDLEUP mii MIIM millis @@ -1174,6 +1179,7 @@ NOACTIVATE NOAGGREGATION NOASYNC NOCLOSEPROCESS +NOCOALESCE NOCOPYBITS nodeca nodiscard @@ -1247,7 +1253,7 @@ oldpath oldtheme oleaut OLECHAR -oledb +OLEDB oledbcommand oledbconnection OLIVEGREEN @@ -1292,6 +1298,7 @@ PARENTRELATIVEPARSING PArgb parray PARTIALCONFIRMATIONDIALOGTITLE +pasteplain pathcch Pathto PAUDIO @@ -1377,7 +1384,7 @@ Prefixer Preinstalled prevhost previewer -previewhandlerframeinfo +PREVIEWHANDLERFRAMEINFO previewpane previouscamera PREVIOUSINSTALLFOLDER @@ -1465,7 +1472,7 @@ rectp rects recyclebin redirectedfrom -redist +Redist redistributable reencode reencoded @@ -1522,6 +1529,8 @@ rgs rhs ricardosantos RIDEV +RIGHTDOWN +RIGHTUP RIGHTSCROLLBAR riid RKey @@ -1529,6 +1538,7 @@ RLO RMENU RNumber roadmap +Roamable robmensching Roboto rooler @@ -1588,7 +1598,7 @@ secauthz secpol Secur securityoverview -segoe +Segoe Sekan SENDCHANGE sendinput @@ -1654,7 +1664,7 @@ SHOWMINNOACTIVE SHOWNA SHOWNOACTIVATE SHOWNORMAL -showwindow +SHOWWINDOW shtypes SICHINT sid @@ -1688,6 +1698,7 @@ Soref SOURCECLIENTAREAONLY SOURCEHEADER sourcesdirectory +SPACEBAR spam spdisp spdlog @@ -1721,7 +1732,7 @@ STARTUPINFO STARTUPINFOEX STARTUPINFOW startupscreen -statflag +STATFLAG STATICEDGE STATSTG stdafx @@ -1733,7 +1744,7 @@ STDMETHODCALLTYPE STDMETHODIMP stefan Stereolithography -stgm +STGM STGMEDIUM sticpl stl @@ -1780,7 +1791,7 @@ SYSICONINDEX sysinfo SYSKEY syskeydown -syskeyup +SYSKEYUP SYSMENU SYSTEMAPPS systemroot @@ -1893,6 +1904,7 @@ UIs Ulaanbaatar ULARGE ULONGLONG +UMsg unassign uncompilable UNCPRIORITY @@ -1972,6 +1984,7 @@ VIDEOINFOHEADER viewbox viewmodel vih +VIRTUALDESK virtualkey visiblecolorformats Visibletrue @@ -2062,7 +2075,7 @@ WINL winmd winmm winmsg -winnt +WINNT winres winrt winsdk @@ -2130,6 +2143,7 @@ wtypes Wubi wuceffectsi WVC +WVk Wwan Wwanpp XAttribute @@ -2142,6 +2156,8 @@ XBUTTONUP XControl xcopy XDocument +XDOWN +XUP XElement XFile XIncrement diff --git a/.github/actions/spell-check/patterns.txt b/.github/actions/spell-check/patterns.txt index 42ceb68d7fd..f17c5fb2aa3 100644 --- a/.github/actions/spell-check/patterns.txt +++ b/.github/actions/spell-check/patterns.txt @@ -56,7 +56,7 @@ https?://(?:(?:www\.|)youtube\.com|youtu.be)/[-a-zA-Z0-9?&=]* /gist\.github\.com/[^/]+/[0-9a-f]+ # msdn -\b(?:download\.visualstudio|docs|msdn)\.microsoft\.com/[-_a-zA-Z0-9()=./]* +\b(?:download\.visualstudio|docs|msdn|learn)\.microsoft\.com/[-_a-zA-Z0-9()=./]* # medium link\.medium\.com/[a-zA-Z0-9]+ diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index ce467a6317f..96efd6cd4a1 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -36,6 +36,8 @@ "modules\\PowerOCR\\PowerToys.PowerOCR.dll", "modules\\PowerOCR\\PowerToys.PowerOCR.exe", + "modules\\PastePlain\\PowerToys.PastePlainModuleInterface.dll", + "modules\\Awake\\PowerToys.AwakeModuleInterface.dll", "modules\\Awake\\PowerToys.Awake.exe", "modules\\Awake\\PowerToys.Awake.dll", diff --git a/PowerToys.sln b/PowerToys.sln index df3b5bf6e7e..725839892c6 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -487,7 +487,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "StlThumbnailProviderCpp", " EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SvgThumbnailProviderCpp", "src\modules\previewpane\SvgThumbnailProviderCpp\SvgThumbnailProviderCpp.vcxproj", "{2BBC9E33-21EC-401C-84DA-BB6590A9B2AA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AllExperiments", "src\common\AllExperiments\AllExperiments.csproj", "{9CE59ED5-7087-4353-88EB-788038A73CEC}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pasteplain", "pasteplain", "{9873BA05-4C41-4819-9283-CF45D795431B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PastePlainModuleInterface", "src\modules\pasteplain\PastePlainModuleInterface\PastePlainModuleInterface.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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -2024,6 +2028,18 @@ Global {2BBC9E33-21EC-401C-84DA-BB6590A9B2AA}.Release|x64.Build.0 = Release|x64 {2BBC9E33-21EC-401C-84DA-BB6590A9B2AA}.Release|x86.ActiveCfg = Release|x64 {2BBC9E33-21EC-401C-84DA-BB6590A9B2AA}.Release|x86.Build.0 = Release|x64 + {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Debug|ARM64.Build.0 = Debug|ARM64 + {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Debug|x64.ActiveCfg = Debug|x64 + {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Debug|x64.Build.0 = Debug|x64 + {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Debug|x86.ActiveCfg = Debug|x64 + {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Debug|x86.Build.0 = Debug|x64 + {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Release|ARM64.ActiveCfg = Release|ARM64 + {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Release|ARM64.Build.0 = Release|ARM64 + {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Release|x64.ActiveCfg = Release|x64 + {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Release|x64.Build.0 = Release|x64 + {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Release|x86.ActiveCfg = Release|x64 + {FC373B24-3293-453C-AAF5-CF2909DCEE6A}.Release|x86.Build.0 = Release|x64 {9CE59ED5-7087-4353-88EB-788038A73CEC}.Debug|ARM64.ActiveCfg = Debug|ARM64 {9CE59ED5-7087-4353-88EB-788038A73CEC}.Debug|ARM64.Build.0 = Debug|ARM64 {9CE59ED5-7087-4353-88EB-788038A73CEC}.Debug|x64.ActiveCfg = Debug|x64 @@ -2206,6 +2222,8 @@ Global {CA5518ED-0458-4B09-8F53-4122B9888655} = {2F305555-C296-497E-AC20-5FA1B237996A} {D6DCC3AE-18C0-488A-B978-BAA9E3CFF09D} = {2F305555-C296-497E-AC20-5FA1B237996A} {2BBC9E33-21EC-401C-84DA-BB6590A9B2AA} = {2F305555-C296-497E-AC20-5FA1B237996A} + {9873BA05-4C41-4819-9283-CF45D795431B} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} + {FC373B24-3293-453C-AAF5-CF2909DCEE6A} = {9873BA05-4C41-4819-9283-CF45D795431B} {9CE59ED5-7087-4353-88EB-788038A73CEC} = {1AFB6476-670D-4E80-A464-657E01DFF482} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/README.md b/README.md index 5224650e17f..d76a97edc21 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline | [Always on Top](https://aka.ms/PowerToysOverview_AoT) | [PowerToys Awake](https://aka.ms/PowerToysOverview_Awake) | [Color Picker](https://aka.ms/PowerToysOverview_ColorPicker) | | [FancyZones](https://aka.ms/PowerToysOverview_FancyZones) | [File Explorer Add-ons](https://aka.ms/PowerToysOverview_FileExplorerAddOns) | [File Locksmith](https://aka.ms/PowerToysOverview_FileLocksmith) | | [Hosts File Editor](https://aka.ms/PowerToysOverview_HostsFileEditor) | [Image Resizer](https://aka.ms/PowerToysOverview_ImageResizer) | [Keyboard Manager](https://aka.ms/PowerToysOverview_KeyboardManager) | -| [Mouse utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | -| [Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | [Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) | [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | -| [Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) | [Video Conference Mute](https://aka.ms/PowerToysOverview_VideoConference) | +| [Mouse utilities](https://aka.ms/PowerToysOverview_MouseUtilities) | [Paste as Plain Text](https://aka.ms/PowerToysOverview_PastePlain) | [PowerRename](https://aka.ms/PowerToysOverview_PowerRename) | +| [PowerToys Run](https://aka.ms/PowerToysOverview_PowerToysRun) | [Quick Accent](https://aka.ms/PowerToysOverview_QuickAccent) | [Screen Ruler](https://aka.ms/PowerToysOverview_ScreenRuler) | +| [Shortcut Guide](https://aka.ms/PowerToysOverview_ShortcutGuide) | [Text Extractor](https://aka.ms/PowerToysOverview_TextExtractor) | [Video Conference Mute](https://aka.ms/PowerToysOverview_VideoConference) | ## Installing and running Microsoft PowerToys diff --git a/installer/PowerToysSetup/Common.wxi b/installer/PowerToysSetup/Common.wxi index 6aad3632b71..9e5b9ea5bcb 100644 --- a/installer/PowerToysSetup/Common.wxi +++ b/installer/PowerToysSetup/Common.wxi @@ -14,7 +14,8 @@ - + + diff --git a/installer/PowerToysSetup/PastePlain.wxs b/installer/PowerToysSetup/PastePlain.wxs new file mode 100644 index 00000000000..cca601a1cab --- /dev/null +++ b/installer/PowerToysSetup/PastePlain.wxs @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + diff --git a/installer/PowerToysSetup/PowerToysInstaller.wixproj b/installer/PowerToysSetup/PowerToysInstaller.wixproj index e4c7dd649e3..256ba27132a 100644 --- a/installer/PowerToysSetup/PowerToysInstaller.wixproj +++ b/installer/PowerToysSetup/PowerToysInstaller.wixproj @@ -74,6 +74,7 @@ call "..\..\publish.cmd" arm64 + diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index 21b6372a117..8ef113c1b38 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -65,6 +65,7 @@ + @@ -478,6 +479,10 @@ + + + + diff --git a/installer/PowerToysSetup/Settings.wxs b/installer/PowerToysSetup/Settings.wxs index 6165e822da9..17b60bf190f 100644 --- a/installer/PowerToysSetup/Settings.wxs +++ b/installer/PowerToysSetup/Settings.wxs @@ -5,9 +5,9 @@ - - - + + + diff --git a/src/common/GPOWrapper/GPOWrapper.cpp b/src/common/GPOWrapper/GPOWrapper.cpp index eb813ab6167..dba7b29a5d4 100644 --- a/src/common/GPOWrapper/GPOWrapper.cpp +++ b/src/common/GPOWrapper/GPOWrapper.cpp @@ -108,6 +108,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation { return static_cast(powertoys_gpo::getConfiguredTextExtractorEnabledValue()); } + GpoRuleConfigured GPOWrapper::GetConfiguredPastePlainEnabledValue() + { + return static_cast(powertoys_gpo::getConfiguredPastePlainEnabledValue()); + } GpoRuleConfigured GPOWrapper::GetConfiguredVideoConferenceMuteEnabledValue() { return static_cast(powertoys_gpo::getConfiguredVideoConferenceMuteEnabledValue()); diff --git a/src/common/GPOWrapper/GPOWrapper.h b/src/common/GPOWrapper/GPOWrapper.h index da74b912d83..fcb559d0347 100644 --- a/src/common/GPOWrapper/GPOWrapper.h +++ b/src/common/GPOWrapper/GPOWrapper.h @@ -33,6 +33,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation static GpoRuleConfigured GetConfiguredScreenRulerEnabledValue(); static GpoRuleConfigured GetConfiguredShortcutGuideEnabledValue(); static GpoRuleConfigured GetConfiguredTextExtractorEnabledValue(); + static GpoRuleConfigured GetConfiguredPastePlainEnabledValue(); static GpoRuleConfigured GetConfiguredVideoConferenceMuteEnabledValue(); static GpoRuleConfigured GetAllowExperimentationValue(); }; diff --git a/src/common/GPOWrapper/GPOWrapper.idl b/src/common/GPOWrapper/GPOWrapper.idl index 5e6deef6d70..51189e48dae 100644 --- a/src/common/GPOWrapper/GPOWrapper.idl +++ b/src/common/GPOWrapper/GPOWrapper.idl @@ -37,6 +37,7 @@ namespace PowerToys static GpoRuleConfigured GetConfiguredScreenRulerEnabledValue(); static GpoRuleConfigured GetConfiguredShortcutGuideEnabledValue(); static GpoRuleConfigured GetConfiguredTextExtractorEnabledValue(); + static GpoRuleConfigured GetConfiguredPastePlainEnabledValue(); static GpoRuleConfigured GetConfiguredVideoConferenceMuteEnabledValue(); static GpoRuleConfigured GetAllowExperimentationValue(); } diff --git a/src/common/GPOWrapperProjection/GPOWrapper.cs b/src/common/GPOWrapperProjection/GPOWrapper.cs index 7017a42084f..108907d1436 100644 --- a/src/common/GPOWrapperProjection/GPOWrapper.cs +++ b/src/common/GPOWrapperProjection/GPOWrapper.cs @@ -41,5 +41,10 @@ public static GpoRuleConfigured GetConfiguredTextExtractorEnabledValue() { return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredTextExtractorEnabledValue(); } + + public static GpoRuleConfigured GetConfiguredPastePlainEnabledValue() + { + return (GpoRuleConfigured)PowerToys.GPOWrapper.GPOWrapper.GetConfiguredPastePlainEnabledValue(); + } } } diff --git a/src/common/interop/shared_constants.h b/src/common/interop/shared_constants.h index 512e5f3039f..43fc2bad20b 100644 --- a/src/common/interop/shared_constants.h +++ b/src/common/interop/shared_constants.h @@ -48,7 +48,7 @@ namespace CommonSharedConstants // Path to the event used by MeasureTool const wchar_t MEASURE_TOOL_TRIGGER_EVENT[] = L"Local\\MeasureToolEvent-3d46745f-09b3-4671-a577-236be7abd199"; - + // Path to the event used by GcodePreviewHandler const wchar_t GCODE_PREVIEW_RESIZE_EVENT[] = L"Local\\PowerToysGcodePreviewResizeEvent-6ff1f9bd-ccbd-4b24-a79f-40a34fb0317d"; diff --git a/src/common/utils/gpo.h b/src/common/utils/gpo.h index ee887c60ea7..1e1bcc2140f 100644 --- a/src/common/utils/gpo.h +++ b/src/common/utils/gpo.h @@ -45,6 +45,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_VIDEO_CONFERENCE_MUTE = L"ConfigureEnabledUtilityVideoConferenceMute"; const std::wstring POLICY_ALLOW_EXPERIMENTATION = L"AllowExperimentation"; @@ -231,6 +232,11 @@ namespace powertoys_gpo { return getConfiguredValue(POLICY_CONFIGURE_ENABLED_TEXT_EXTRACTOR); } + inline gpo_rule_configured_t getConfiguredPastePlainEnabledValue() + { + return getConfiguredValue(POLICY_CONFIGURE_ENABLED_PASTE_PLAIN); + } + inline gpo_rule_configured_t getConfiguredVideoConferenceMuteEnabledValue() { return getConfiguredValue(POLICY_CONFIGURE_ENABLED_VIDEO_CONFERENCE_MUTE); diff --git a/src/gpo/assets/PowerToys.admx b/src/gpo/assets/PowerToys.admx index 00b0bb2b9dd..4a7fbadafd0 100644 --- a/src/gpo/assets/PowerToys.admx +++ b/src/gpo/assets/PowerToys.admx @@ -217,6 +217,16 @@ + + + + + + + + + + diff --git a/src/gpo/assets/en-US/PowerToys.adml b/src/gpo/assets/en-US/PowerToys.adml index 17c379b6912..9dadbafef22 100644 --- a/src/gpo/assets/en-US/PowerToys.adml +++ b/src/gpo/assets/en-US/PowerToys.adml @@ -55,6 +55,7 @@ If this setting is disabled, experimentation is not allowed. Find My Mouse: Configure enabled state Mouse Highlighter: Configure enabled state Mouse Pointer Crosshairs: Configure enabled state + Paste as Plain Text: Configure enabled state Power Rename: Configure enabled state PowerToys Run: Configure enabled state Quick Accent: Configure enabled state diff --git a/src/modules/pasteplain/PastePlainModuleInterface/PastePlain.base.rc b/src/modules/pasteplain/PastePlainModuleInterface/PastePlain.base.rc new file mode 100644 index 00000000000..b30e3923c98 --- /dev/null +++ b/src/modules/pasteplain/PastePlainModuleInterface/PastePlain.base.rc @@ -0,0 +1,40 @@ +#include +#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 diff --git a/src/modules/pasteplain/PastePlainModuleInterface/PastePlainConstants.h b/src/modules/pasteplain/PastePlainModuleInterface/PastePlainConstants.h new file mode 100644 index 00000000000..d72f7b3242f --- /dev/null +++ b/src/modules/pasteplain/PastePlainModuleInterface/PastePlainConstants.h @@ -0,0 +1,8 @@ +#pragma once +#include + +namespace PastePlainConstants +{ + // Name of the powertoy module. + inline const std::wstring ModuleKey = L"PastePlain"; +} \ No newline at end of file diff --git a/src/modules/pasteplain/PastePlainModuleInterface/PastePlainModuleInterface.vcxproj b/src/modules/pasteplain/PastePlainModuleInterface/PastePlainModuleInterface.vcxproj new file mode 100644 index 00000000000..8e1b3707b7c --- /dev/null +++ b/src/modules/pasteplain/PastePlainModuleInterface/PastePlainModuleInterface.vcxproj @@ -0,0 +1,85 @@ + + + + + + + + 15.0 + Win32Proj + {FC373B24-3293-453C-AAF5-CF2909DCEE6A} + PastePlain + PastePlainModuleInterface + PowerToys.PastePlainModuleInterface + + + + DynamicLibrary + v143 + + + + + + + + + + + + $(SolutionDir)$(Platform)\$(Configuration)\modules\PastePlain\ + + + + EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + ..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories) + + + $(OutDir)$(TargetName)$(TargetExt) + + + + + + + + + + + + + + Create + + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + {6955446d-23f7-4023-9bb3-8657f904af99} + + + + + + + + + Designer + + + + + + + + + + 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}. + + + + + \ No newline at end of file diff --git a/src/modules/pasteplain/PastePlainModuleInterface/PastePlainModuleInterface.vcxproj.filters b/src/modules/pasteplain/PastePlainModuleInterface/PastePlainModuleInterface.vcxproj.filters new file mode 100644 index 00000000000..9f2d585a7de --- /dev/null +++ b/src/modules/pasteplain/PastePlainModuleInterface/PastePlainModuleInterface.vcxproj.filters @@ -0,0 +1,62 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {875a08c6-f610-4667-bd0f-80171ed96072} + + + + + Header Files + + + Generated Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Resource Files + + + + Resource Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/src/modules/pasteplain/PastePlainModuleInterface/Resources.resx b/src/modules/pasteplain/PastePlainModuleInterface/Resources.resx new file mode 100644 index 00000000000..3e27759bfd8 --- /dev/null +++ b/src/modules/pasteplain/PastePlainModuleInterface/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Paste As Plain Text + + + PowerToys integration to paste clipboard contents as plain text + + \ No newline at end of file diff --git a/src/modules/pasteplain/PastePlainModuleInterface/dllmain.cpp b/src/modules/pasteplain/PastePlainModuleInterface/dllmain.cpp new file mode 100644 index 00000000000..e3bfdb5600b --- /dev/null +++ b/src/modules/pasteplain/PastePlainModuleInterface/dllmain.cpp @@ -0,0 +1,495 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "pch.h" + +#include +#include "trace.h" +#include "Generated Files/resource.h" +#include +#include +#include + +#include "PastePlainConstants.h" +#include +#include +#include + +BOOL APIENTRY DllMain(HMODULE /*hModule*/, + DWORD ul_reason_for_call, + LPVOID /*lpReserved*/) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + Trace::RegisterProvider(); + break; + 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 PastePlain : 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 PastePlain + 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(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE)); + } + catch (...) + { + Logger::error("Failed to initialize PastePlain start shortcut"); + } + } + else + { + Logger::info("PastePlain settings are empty"); + } + + if (!m_hotkey.key) + { + Logger::info("PastePlain is going to use default shortcut"); + m_hotkey.win = true; + m_hotkey.alt = false; + m_hotkey.shift = false; + m_hotkey.ctrl = true; + m_hotkey.key = 'V'; + } + } + + bool is_process_running() + { + return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT; + } + + void launch_process() + { + Logger::trace(L"Starting PastePlain 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\\PastePlain\\PowerToys.PastePlain.exe"; + sei.nShow = SW_SHOWNORMAL; + sei.lpParameters = executable_args.data(); + if (ShellExecuteExW(&sei)) + { + Logger::trace("Successfully started the PastePlain process"); + } + else + { + Logger::error(L"PastePlain 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&) + { + 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. + } + } + + void try_inject_modifier_key_up(std::vector &inputs, short modifier) + { + // Most significant bit is set if key is down + if ((GetAsyncKeyState(static_cast(modifier)) & 0x8000) != 0) + { + INPUT input_event = {}; + input_event.type = INPUT_KEYBOARD; + input_event.ki.wVk = modifier; + input_event.ki.dwFlags = KEYEVENTF_KEYUP; + inputs.push_back(input_event); + } + } + + void try_to_paste_as_plain_text() + { + std::wstring clipboard_text; + + { + // Read clipboard data begin + + if (!OpenClipboard(NULL)) + { + DWORD errorCode = GetLastError(); + auto errorMessage = get_last_error_message(errorCode); + Logger::error(L"Couldn't open the clipboard to get the text. {}", errorMessage.has_value() ? errorMessage.value() : L""); + Trace::PastePlainError(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"read.OpenClipboard"); + return; + } + HANDLE h_clipboard_data = GetClipboardData(CF_UNICODETEXT); + + if (h_clipboard_data == NULL) + { + DWORD errorCode = GetLastError(); + auto errorMessage = get_last_error_message(errorCode); + Logger::error(L"Failed to get clipboard data. {}", errorMessage.has_value() ? errorMessage.value() : L""); + Trace::PastePlainError(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"read.GetClipboardData"); + CloseClipboard(); + return; + } + + wchar_t* pch_data= static_cast(GlobalLock(h_clipboard_data)); + + if (NULL == pch_data ) + { + DWORD errorCode = GetLastError(); + auto errorMessage = get_last_error_message(errorCode); + Logger::error(L"Couldn't lock the buffer to get the unformatted text from the clipboard. {}", errorMessage.has_value() ? errorMessage.value() : L""); + Trace::PastePlainError(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"read.GlobalLock"); + CloseClipboard(); + return; + } + + clipboard_text = pch_data; + GlobalUnlock(h_clipboard_data); + + CloseClipboard(); + // Read clipboard data end + } + + { + // Copy text to clipboard begin + UINT no_clipboard_history_or_roaming_format = 0; + + // Get the format identifier for not adding the data to the clipboard history or roaming. + // https://learn.microsoft.com/en-us/windows/win32/dataxchg/clipboard-formats#cloud-clipboard-and-clipboard-history-formats + if (0 == (no_clipboard_history_or_roaming_format = RegisterClipboardFormat(L"ExcludeClipboardContentFromMonitorProcessing"))) + { + DWORD errorCode = GetLastError(); + auto errorMessage = get_last_error_message(errorCode); + Logger::error(L"Couldn't get the clipboard data format type that would allow excluding the data from the clipboard history / roaming. {}", errorMessage.has_value() ? errorMessage.value() : L""); + Trace::PastePlainError(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"write.RegisterClipboardFormat"); + return; + } + + if (!OpenClipboard(NULL)) + { + DWORD errorCode = GetLastError(); + auto errorMessage = get_last_error_message(errorCode); + Logger::error(L"Couldn't open the clipboard to copy the unformatted text. {}", errorMessage.has_value() ? errorMessage.value() : L""); + Trace::PastePlainError(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"write.OpenClipboard"); + return; + } + + HGLOBAL h_clipboard_data; + + if (NULL == (h_clipboard_data = GlobalAlloc(GMEM_MOVEABLE, (clipboard_text.length() + 1) * sizeof(wchar_t)))) + { + DWORD errorCode = GetLastError(); + auto errorMessage = get_last_error_message(errorCode); + Logger::error(L"Couldn't allocate a buffer for the unformatted text. {}", errorMessage.has_value() ? errorMessage.value() : L""); + Trace::PastePlainError(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"write.GlobalAlloc"); + CloseClipboard(); + return; + } + wchar_t* pch_data = static_cast(GlobalLock(h_clipboard_data)); + + if (NULL == pch_data) + { + DWORD errorCode = GetLastError(); + auto errorMessage = get_last_error_message(errorCode); + Logger::error(L"Couldn't lock the buffer to send the unformatted text to the clipboard. {}", errorMessage.has_value() ? errorMessage.value() : L""); + GlobalFree(h_clipboard_data); + CloseClipboard(); + Trace::PastePlainError(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"write.GlobalLock"); + return; + } + + wcscpy_s(pch_data, clipboard_text.length() + 1, clipboard_text.c_str()); + + EmptyClipboard(); + + if (NULL == SetClipboardData(CF_UNICODETEXT, h_clipboard_data)) + { + DWORD errorCode = GetLastError(); + auto errorMessage = get_last_error_message(errorCode); + Logger::error(L"Couldn't set the clipboard data to the unformatted text. {}", errorMessage.has_value() ? errorMessage.value() : L""); + GlobalUnlock(h_clipboard_data); + GlobalFree(h_clipboard_data); + CloseClipboard(); + Trace::PastePlainError(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"write.SetClipboardData"); + return; + } + + // Don't show in history or allow data roaming. + SetClipboardData(no_clipboard_history_or_roaming_format, 0); + + CloseClipboard(); + // Copy text to clipboard end + } + { + // Clear kb state and send Ctrl+V begin + + // we can assume that the last pressed key is... + // (1) not a modifier key and + // (2) marked as handled (so it never gets a key down input event). + // So, let's check which modifiers were pressed, + // and, if they were, inject a key up event for each of them + std::vector inputs; + try_inject_modifier_key_up(inputs, VK_LCONTROL); + try_inject_modifier_key_up(inputs, VK_RCONTROL); + try_inject_modifier_key_up(inputs, VK_LWIN); + try_inject_modifier_key_up(inputs, VK_RWIN); + try_inject_modifier_key_up(inputs, VK_LSHIFT); + try_inject_modifier_key_up(inputs, VK_RSHIFT); + try_inject_modifier_key_up(inputs, VK_LMENU); + try_inject_modifier_key_up(inputs, VK_RMENU); + + // send Ctrl+V (key downs and key ups) + { + INPUT input_event = {}; + input_event.type = INPUT_KEYBOARD; + input_event.ki.wVk = VK_CONTROL; + inputs.push_back(input_event); + } + + { + INPUT input_event = {}; + input_event.type = INPUT_KEYBOARD; + input_event.ki.wVk = 0x56; // V + inputs.push_back(input_event); + } + + { + INPUT input_event = {}; + input_event.type = INPUT_KEYBOARD; + input_event.ki.wVk = 0x56; // V + input_event.ki.dwFlags = KEYEVENTF_KEYUP; + inputs.push_back(input_event); + } + + { + INPUT input_event = {}; + input_event.type = INPUT_KEYBOARD; + input_event.ki.wVk = VK_CONTROL; + input_event.ki.dwFlags = KEYEVENTF_KEYUP; + inputs.push_back(input_event); + } + + auto uSent = SendInput(static_cast(inputs.size()), inputs.data(), sizeof(INPUT)); + if (uSent != inputs.size()) + { + DWORD errorCode = GetLastError(); + auto errorMessage = get_last_error_message(errorCode); + Logger::error(L"SendInput failed. Expected to send {} inputs and sent only {}. {}", inputs.size(), uSent, errorMessage.has_value() ? errorMessage.value() : L""); + Trace::PastePlainError(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"input.SendInput"); + return; + } + + // Clear kb state and send Ctrl+V end + } + Trace::PastePlainSuccess(); + } + +public: + PastePlain() + { + app_name = GET_RESOURCE_STRING(IDS_PASTEPLAIN_NAME); + app_key = PastePlainConstants::ModuleKey; + LoggerHelpers::init_logger(app_key, L"ModuleInterface", "PastePlain"); + init_settings(); + } + + ~PastePlain() + { + if (m_enabled) + { + } + m_enabled = false; + } + + // Destroy the powertoy and free memory + virtual void destroy() override + { + Logger::trace("PastePlain::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(); + } + + // Return the configured status for the gpo policy for the module + virtual powertoys_gpo::gpo_rule_configured_t gpo_policy_enabled_configuration() override + { + return powertoys_gpo::getConfiguredPastePlainEnabledValue(); + } + + virtual bool get_config(wchar_t* buffer, int* buffer_size) override + { + HINSTANCE hinstance = reinterpret_cast(&__ImageBase); + + // Create a Settings object. + PowerToysSettings::Settings settings(hinstance, get_name()); + settings.set_description(GET_RESOURCE_STRING(IDS_PASTEPLAIN_SETTINGS_DESC)); + + settings.set_overview_link(L"https://aka.ms/PowerToysOverview_PastePlain"); + + 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&) + { + // Improper JSON. + } + } + + virtual void enable() + { + Logger::trace("PastePlain::enable()"); + m_enabled = true; + Trace::EnablePastePlain(true); + }; + + virtual void disable() + { + Logger::trace("PastePlain::disable()"); + m_enabled = false; + Trace::EnablePastePlain(false); + } + + + + virtual bool on_hotkey(size_t /*hotkeyId*/) override + { + if (m_enabled) + { + Logger::trace(L"PastePlain hotkey pressed"); + + std::thread([=]() { + // hotkey work should be kept to a minimum, or Windows might deregister our low level keyboard hook. + // Move work to another thread. + try_to_paste_as_plain_text(); + }).detach(); + + Trace::PastePlainInvoked(); + 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; + } + + virtual void send_settings_telemetry() override + { + Logger::info("Send settings telemetry"); + Trace::SettingsTelemetry(m_hotkey); + } +}; + +extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() +{ + return new PastePlain(); +} diff --git a/src/modules/pasteplain/PastePlainModuleInterface/packages.config b/src/modules/pasteplain/PastePlainModuleInterface/packages.config new file mode 100644 index 00000000000..48319b8c957 --- /dev/null +++ b/src/modules/pasteplain/PastePlainModuleInterface/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/modules/pasteplain/PastePlainModuleInterface/pch.cpp b/src/modules/pasteplain/PastePlainModuleInterface/pch.cpp new file mode 100644 index 00000000000..ce9b73991b7 --- /dev/null +++ b/src/modules/pasteplain/PastePlainModuleInterface/pch.cpp @@ -0,0 +1,2 @@ +#include "pch.h" + diff --git a/src/modules/pasteplain/PastePlainModuleInterface/pch.h b/src/modules/pasteplain/PastePlainModuleInterface/pch.h new file mode 100644 index 00000000000..eddac0fdc1f --- /dev/null +++ b/src/modules/pasteplain/PastePlainModuleInterface/pch.h @@ -0,0 +1,7 @@ +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/src/modules/pasteplain/PastePlainModuleInterface/resource.base.h b/src/modules/pasteplain/PastePlainModuleInterface/resource.base.h new file mode 100644 index 00000000000..e2a93202be0 --- /dev/null +++ b/src/modules/pasteplain/PastePlainModuleInterface/resource.base.h @@ -0,0 +1,13 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by PowerOCR.rc + +////////////////////////////// +// Non-localizable + +#define FILE_DESCRIPTION "PowerToys PastePlain" +#define INTERNAL_NAME "PowerToys.PastePlainModuleInterface" +#define ORIGINAL_FILENAME "PowerToys.PastePlainModuleInterface.dll" + +// Non-localizable +////////////////////////////// diff --git a/src/modules/pasteplain/PastePlainModuleInterface/trace.cpp b/src/modules/pasteplain/PastePlainModuleInterface/trace.cpp new file mode 100644 index 00000000000..a6f06b1ca9e --- /dev/null +++ b/src/modules/pasteplain/PastePlainModuleInterface/trace.cpp @@ -0,0 +1,82 @@ +#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 PastePlain enabled or disabled +void Trace::EnablePastePlain(const bool enabled) noexcept +{ + TraceLoggingWrite( + g_hProvider, + "PastePlain_EnablePastePlain", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), + TraceLoggingBoolean(enabled, "Enabled")); +} + +// Log if the user has invoked PastePlain +void Trace::PastePlainInvoked() noexcept +{ + TraceLoggingWrite( + g_hProvider, + "PastePlain_InvokePastePlain", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} + +// Log if a PastePlain invocation has succeeded +void Trace::PastePlainSuccess() noexcept +{ + TraceLoggingWrite( + g_hProvider, + "PastePlain_Success", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE)); +} + +// Log if an error occurs in PastePlain +void Trace::PastePlainError(const DWORD errorCode, std::wstring errorMessage, std::wstring methodName) noexcept +{ + TraceLoggingWrite( + g_hProvider, + "PastePlain_Error", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), + TraceLoggingValue(methodName.c_str(), "MethodName"), + TraceLoggingValue(errorCode, "ErrorCode"), + TraceLoggingValue(errorMessage.c_str(), "ErrorMessage")); +} + +// Event to send settings telemetry. +void Trace::SettingsTelemetry(PowertoyModuleIface::Hotkey& hotkey) noexcept +{ + std::wstring hotKeyStr = + std::wstring(hotkey.win ? L"Win + " : L"") + + std::wstring(hotkey.ctrl ? L"Ctrl + " : L"") + + std::wstring(hotkey.shift ? L"Shift + " : L"") + + std::wstring(hotkey.alt ? L"Alt + " : L"") + + std::wstring(L"VK ") + std::to_wstring(hotkey.key); + + TraceLoggingWrite( + g_hProvider, + "PastePlain_Settings", + ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance), + TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE), + TraceLoggingWideString(hotKeyStr.c_str(), "HotKey") + ); +} diff --git a/src/modules/pasteplain/PastePlainModuleInterface/trace.h b/src/modules/pasteplain/PastePlainModuleInterface/trace.h new file mode 100644 index 00000000000..6b65d0f010f --- /dev/null +++ b/src/modules/pasteplain/PastePlainModuleInterface/trace.h @@ -0,0 +1,24 @@ +#pragma once +#include + +class Trace +{ +public: + static void RegisterProvider(); + static void UnregisterProvider(); + + // Log if the user has PastePlain enabled or disabled + static void EnablePastePlain(const bool enabled) noexcept; + + // Log if the user has invoked PastePlain + static void PastePlainInvoked() noexcept; + + // Log if a PastePlain invocation has succeeded + static void Trace::PastePlainSuccess() noexcept; + + // Log if an error occurs in PastePlain + static void Trace::PastePlainError(const DWORD errorCode, std::wstring errorMessage, std::wstring methodName) noexcept; + + // Event to send settings telemetry. + static void Trace::SettingsTelemetry(PowertoyModuleIface::Hotkey& hotkey) noexcept; +}; diff --git a/src/runner/main.cpp b/src/runner/main.cpp index 442eab5698f..bedcba612fe 100644 --- a/src/runner/main.cpp +++ b/src/runner/main.cpp @@ -162,6 +162,7 @@ int runner(bool isProcessElevated, bool openSettings, std::string settingsWindow L"modules/MouseUtils/PowerToys.MousePointerCrosshairs.dll", L"modules/PowerAccent/PowerToys.PowerAccentModuleInterface.dll", L"modules/PowerOCR/PowerToys.PowerOCRModuleInterface.dll", + L"modules/PastePlain/PowerToys.PastePlainModuleInterface.dll", L"modules/FileLocksmith/PowerToys.FileLocksmithExt.dll", L"modules/MeasureTool/PowerToys.MeasureToolModuleInterface.dll", L"modules/Hosts/PowerToys.HostsModuleInterface.dll", diff --git a/src/settings-ui/Settings.UI.Library/EnabledModules.cs b/src/settings-ui/Settings.UI.Library/EnabledModules.cs index ea055fb4cd0..f68735ba3e5 100644 --- a/src/settings-ui/Settings.UI.Library/EnabledModules.cs +++ b/src/settings-ui/Settings.UI.Library/EnabledModules.cs @@ -279,6 +279,23 @@ public bool PowerOCR } } + private bool pastePlain = true; + + [JsonPropertyName("PastePlain")] + public bool PastePlain + { + get => pastePlain; + set + { + if (pastePlain != value) + { + LogTelemetryEvent(value); + pastePlain = value; + NotifyChange(); + } + } + } + private bool measureTool = true; [JsonPropertyName("Measure Tool")] diff --git a/src/settings-ui/Settings.UI.Library/PastePlainProperties.cs b/src/settings-ui/Settings.UI.Library/PastePlainProperties.cs new file mode 100644 index 00000000000..fc7c5c86392 --- /dev/null +++ b/src/settings-ui/Settings.UI.Library/PastePlainProperties.cs @@ -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 PastePlainProperties + { + public PastePlainProperties() + { + ActivationShortcut = new HotkeySettings(true, true, false, false, 0x56); // Ctrl+Win+V + } + + public HotkeySettings ActivationShortcut { get; set; } + + public override string ToString() + => JsonSerializer.Serialize(this); + } +} diff --git a/src/settings-ui/Settings.UI.Library/PastePlainSettings.cs b/src/settings-ui/Settings.UI.Library/PastePlainSettings.cs new file mode 100644 index 00000000000..bf0ee98cfe0 --- /dev/null +++ b/src/settings-ui/Settings.UI.Library/PastePlainSettings.cs @@ -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 PastePlainSettings : BasePTModuleSettings, ISettingsConfig + { + public const string ModuleName = "PastePlain"; + + [JsonPropertyName("properties")] + public PastePlainProperties Properties { get; set; } + + public PastePlainSettings() + { + Properties = new PastePlainProperties(); + 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; + } +} diff --git a/src/settings-ui/Settings.UI/App.xaml.cs b/src/settings-ui/Settings.UI/App.xaml.cs index 41486f5d17c..737c73a3783 100644 --- a/src/settings-ui/Settings.UI/App.xaml.cs +++ b/src/settings-ui/Settings.UI/App.xaml.cs @@ -151,6 +151,7 @@ protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs ar case "VideoConference": StartupPage = typeof(Views.VideoConferencePage); break; case "MeasureTool": StartupPage = typeof(Views.MeasureToolPage); break; case "Hosts": StartupPage = typeof(Views.HostsPage); break; + case "PastePlain": StartupPage = typeof(Views.PastePlainPage); break; default: Debug.Assert(false, "Unexpected SettingsWindow argument value"); break; } diff --git a/src/settings-ui/Settings.UI/Assets/FluentIcons/FluentIconsPastePlain.png b/src/settings-ui/Settings.UI/Assets/FluentIcons/FluentIconsPastePlain.png new file mode 100644 index 00000000000..fe16ae575dd Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/FluentIcons/FluentIconsPastePlain.png differ diff --git a/src/settings-ui/Settings.UI/Assets/Modules/OOBE/PastePlain.gif b/src/settings-ui/Settings.UI/Assets/Modules/OOBE/PastePlain.gif new file mode 100644 index 00000000000..a9f6c089b7a Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/Modules/OOBE/PastePlain.gif differ diff --git a/src/settings-ui/Settings.UI/Assets/Modules/PastePlain.png b/src/settings-ui/Settings.UI/Assets/Modules/PastePlain.png new file mode 100644 index 00000000000..cd5860fe7f8 Binary files /dev/null and b/src/settings-ui/Settings.UI/Assets/Modules/PastePlain.png differ diff --git a/src/settings-ui/Settings.UI/MainWindow.xaml.cs b/src/settings-ui/Settings.UI/MainWindow.xaml.cs index 4fef64531c4..f4b9e2746a0 100644 --- a/src/settings-ui/Settings.UI/MainWindow.xaml.cs +++ b/src/settings-ui/Settings.UI/MainWindow.xaml.cs @@ -123,6 +123,9 @@ public MainWindow(bool isDark, bool createHidden = false) case "MousePointerCrosshairs": needToUpdate = generalSettingsConfig.Enabled.MousePointerCrosshairs != isEnabled; generalSettingsConfig.Enabled.MousePointerCrosshairs = isEnabled; break; + case "PastePlain": + needToUpdate = generalSettingsConfig.Enabled.PastePlain != isEnabled; + generalSettingsConfig.Enabled.PastePlain = isEnabled; break; case "PowerRename": needToUpdate = generalSettingsConfig.Enabled.PowerRename != isEnabled; generalSettingsConfig.Enabled.PowerRename = isEnabled; break; diff --git a/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs b/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs index 148de19256e..b7ceb31266a 100644 --- a/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs +++ b/src/settings-ui/Settings.UI/OOBE/Enums/PowerToysModules.cs @@ -24,6 +24,7 @@ public enum PowerToysModules VideoConference, MeasureTool, Hosts, + PastePlain, WhatsNew, } } diff --git a/src/settings-ui/Settings.UI/OOBE/Views/OobePastePlain.xaml b/src/settings-ui/Settings.UI/OOBE/Views/OobePastePlain.xaml new file mode 100644 index 00000000000..f85d7928a66 --- /dev/null +++ b/src/settings-ui/Settings.UI/OOBE/Views/OobePastePlain.xaml @@ -0,0 +1,44 @@ + + + + + + + + + + + +