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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/settings-ui/Settings.UI/OOBE/Views/OobePastePlain.xaml.cs b/src/settings-ui/Settings.UI/OOBE/Views/OobePastePlain.xaml.cs
new file mode 100644
index 00000000000..e53920307db
--- /dev/null
+++ b/src/settings-ui/Settings.UI/OOBE/Views/OobePastePlain.xaml.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.PowerToys.Settings.UI.OOBE.Enums;
+using Microsoft.PowerToys.Settings.UI.OOBE.ViewModel;
+using Microsoft.PowerToys.Settings.UI.Views;
+using Microsoft.UI.Xaml.Controls;
+using Microsoft.UI.Xaml.Navigation;
+
+namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
+{
+ public sealed partial class OobePastePlain : Page
+ {
+ public OobePowerToysModule ViewModel { get; set; }
+
+ public OobePastePlain()
+ {
+ this.InitializeComponent();
+ ViewModel = new OobePowerToysModule(OobeShellPage.OobeShellHandler.Modules[(int)PowerToysModules.PastePlain]);
+ DataContext = ViewModel;
+ }
+
+ private void SettingsLaunchButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
+ {
+ if (OobeShellPage.OpenMainWindowCallback != null)
+ {
+ OobeShellPage.OpenMainWindowCallback(typeof(PastePlainPage));
+ }
+
+ ViewModel.LogOpeningSettingsEvent();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ ViewModel.LogOpeningModuleEvent();
+ HotkeyControl.Keys = SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Properties.ActivationShortcut.GetKeysList();
+ }
+
+ protected override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ ViewModel.LogClosingModuleEvent();
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/OOBE/Views/OobeShellPage.xaml b/src/settings-ui/Settings.UI/OOBE/Views/OobeShellPage.xaml
index 865c6c649b3..73ec67abced 100644
--- a/src/settings-ui/Settings.UI/OOBE/Views/OobeShellPage.xaml
+++ b/src/settings-ui/Settings.UI/OOBE/Views/OobeShellPage.xaml
@@ -68,6 +68,10 @@
x:Uid="Shell_MouseUtilities"
Icon="{ui:BitmapIcon Source=/Assets/FluentIcons/FluentIconsMouseUtils.png}"
Tag="MouseUtils" />
+
Learn more about Mouse utilities
Mouse utilities is a product name, do not loc
+
+ Learn more about Paste as Plain Text
+ Paste as Plain Text is the name of the module.
+
Learn more about File Explorer add-ons
File Explorer is a product name, localize as Windows does
@@ -2934,6 +2938,31 @@ Activate by holding the key for the character you want to add an accent to, then
The system administrator has disabled experimentation.
+
+ Paste As Plain Text
+ Product name: Navigation view item name for Paste as Plain Text
+
+
+ Paste As Plain Text is a quick shortcut for pasting the text contents of your clipboard without formatting. Note: the formatted text in the clipboard is replaced with the unformatted text.
+
+
+ Paste As Plain Text
+
+
+ cancel
+
+
+ Enable Paste As Plain Text
+
+
+ Paste As Plain Text strips rich formatting from your clipboard data and pastes it as non-formatted text.
+
+
+ Paste As Plain Text
+
+
+ to paste your clipboard data as plain text. Note: this will replace the formatted text in your clipboard with the plain text.
+
All apps
diff --git a/src/settings-ui/Settings.UI/ViewModels/Flyout/AllAppsViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/Flyout/AllAppsViewModel.cs
index af05e8b721d..74d7a3b37c9 100644
--- a/src/settings-ui/Settings.UI/ViewModels/Flyout/AllAppsViewModel.cs
+++ b/src/settings-ui/Settings.UI/ViewModels/Flyout/AllAppsViewModel.cs
@@ -88,6 +88,11 @@ public AllAppsViewModel(ISettingsRepository settingsRepository,
FlyoutMenuItems.Add(new FlyoutMenuItem() { Label = resourceLoader.GetString("MouseUtils_MousePointerCrosshairs/Header"), IsEnabled = generalSettingsConfig.Enabled.MousePointerCrosshairs, Tag = "MousePointerCrosshairs", Icon = "ms-appx:///Assets/FluentIcons/FluentIconsMouseCrosshairs.png", EnabledChangedCallback = EnabledChangedOnUI });
}
+ if ((gpo = GPOWrapper.GetConfiguredPastePlainEnabledValue()) != GpoRuleConfigured.Disabled && gpo != GpoRuleConfigured.Enabled)
+ {
+ FlyoutMenuItems.Add(new FlyoutMenuItem() { Label = resourceLoader.GetString("PastePlain/ModuleTitle"), IsEnabled = generalSettingsConfig.Enabled.PastePlain, Tag = "PastePlain", Icon = "ms-appx:///Assets/FluentIcons/FluentIconsPastePlain.png", EnabledChangedCallback = EnabledChangedOnUI });
+ }
+
if ((gpo = GPOWrapper.GetConfiguredPowerRenameEnabledValue()) != GpoRuleConfigured.Disabled && gpo != GpoRuleConfigured.Enabled)
{
FlyoutMenuItems.Add(new FlyoutMenuItem() { Label = resourceLoader.GetString("PowerRename/ModuleTitle"), IsEnabled = generalSettingsConfig.Enabled.PowerRename, Tag = "PowerRename", Icon = "ms-appx:///Assets/FluentIcons/FluentIconsPowerRename.png", EnabledChangedCallback = EnabledChangedOnUI });
@@ -149,6 +154,7 @@ private void ModuleEnabledChangedOnSettingsPage()
case "KeyboardManager": item.IsEnabled = generalSettingsConfig.Enabled.KeyboardManager; break;
case "MouseHighlighter": item.IsEnabled = generalSettingsConfig.Enabled.MouseHighlighter; break;
case "MousePointerCrosshairs": item.IsEnabled = generalSettingsConfig.Enabled.MousePointerCrosshairs; break;
+ case "PastePlain": item.IsEnabled = generalSettingsConfig.Enabled.PastePlain; break;
case "PowerRename": item.IsEnabled = generalSettingsConfig.Enabled.PowerRename; break;
case "PowerLauncher": item.IsEnabled = generalSettingsConfig.Enabled.PowerLauncher; break;
case "PowerAccent": item.IsEnabled = generalSettingsConfig.Enabled.PowerAccent; break;
diff --git a/src/settings-ui/Settings.UI/ViewModels/PastePlainViewModel.cs b/src/settings-ui/Settings.UI/ViewModels/PastePlainViewModel.cs
new file mode 100644
index 00000000000..40a9ed780b6
--- /dev/null
+++ b/src/settings-ui/Settings.UI/ViewModels/PastePlainViewModel.cs
@@ -0,0 +1,196 @@
+// 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.Text.Json;
+using System.Timers;
+using global::PowerToys.GPOWrapper;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.PowerToys.Settings.UI.Library.Helpers;
+using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
+
+namespace Microsoft.PowerToys.Settings.UI.ViewModels
+{
+ public class PastePlainViewModel : Observable, IDisposable
+ {
+ private bool disposedValue;
+
+ // Delay saving of settings in order to avoid calling save multiple times and hitting file in use exception. If there is no other request to save settings in given interval, we proceed to save it, otherwise we schedule saving it after this interval
+ private const int SaveSettingsDelayInMs = 500;
+
+ private GeneralSettings GeneralSettingsConfig { get; set; }
+
+ private readonly ISettingsUtils _settingsUtils;
+ private readonly object _delayedActionLock = new object();
+
+ private readonly PastePlainSettings _pastePlainSettings;
+ private Timer _delayedTimer;
+
+ private GpoRuleConfigured _enabledGpoRuleConfiguration;
+ private bool _enabledStateIsGPOConfigured;
+ private bool _isEnabled;
+
+ private Func SendConfigMSG { get; }
+
+ public PastePlainViewModel(
+ ISettingsUtils settingsUtils,
+ ISettingsRepository settingsRepository,
+ ISettingsRepository pastePlainSettingsRepository,
+ Func ipcMSGCallBackFunc)
+ {
+ // To obtain the general settings configurations of PowerToys Settings.
+ if (settingsRepository == null)
+ {
+ throw new ArgumentNullException(nameof(settingsRepository));
+ }
+
+ GeneralSettingsConfig = settingsRepository.SettingsConfig;
+
+ // To obtain the settings configurations of Fancy zones.
+ if (settingsRepository == null)
+ {
+ throw new ArgumentNullException(nameof(settingsRepository));
+ }
+
+ _settingsUtils = settingsUtils ?? throw new ArgumentNullException(nameof(settingsUtils));
+
+ if (pastePlainSettingsRepository == null)
+ {
+ throw new ArgumentNullException(nameof(pastePlainSettingsRepository));
+ }
+
+ _pastePlainSettings = pastePlainSettingsRepository.SettingsConfig;
+
+ InitializeEnabledValue();
+
+ // set the callback functions value to hangle outgoing IPC message.
+ SendConfigMSG = ipcMSGCallBackFunc;
+
+ _delayedTimer = new Timer();
+ _delayedTimer.Interval = SaveSettingsDelayInMs;
+ _delayedTimer.Elapsed += DelayedTimer_Tick;
+ _delayedTimer.AutoReset = false;
+ }
+
+ private void InitializeEnabledValue()
+ {
+ _enabledGpoRuleConfiguration = GPOWrapper.GetConfiguredPastePlainEnabledValue();
+ if (_enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled || _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled)
+ {
+ // Get the enabled state from GPO.
+ _enabledStateIsGPOConfigured = true;
+ _isEnabled = _enabledGpoRuleConfiguration == GpoRuleConfigured.Enabled;
+ }
+ else
+ {
+ _isEnabled = GeneralSettingsConfig.Enabled.PastePlain;
+ }
+ }
+
+ public bool IsEnabled
+ {
+ get => _isEnabled;
+ set
+ {
+ if (_enabledStateIsGPOConfigured)
+ {
+ // If it's GPO configured, shouldn't be able to change this state.
+ return;
+ }
+
+ if (_isEnabled != value)
+ {
+ _isEnabled = value;
+ OnPropertyChanged(nameof(IsEnabled));
+
+ // Set the status of PastePlain in the general settings
+ GeneralSettingsConfig.Enabled.PastePlain = value;
+ var outgoing = new OutGoingGeneralSettings(GeneralSettingsConfig);
+
+ SendConfigMSG(outgoing.ToString());
+ }
+ }
+ }
+
+ public bool IsEnabledGpoConfigured
+ {
+ get => _enabledStateIsGPOConfigured;
+ }
+
+ public HotkeySettings ActivationShortcut
+ {
+ get => _pastePlainSettings.Properties.ActivationShortcut;
+ set
+ {
+ if (_pastePlainSettings.Properties.ActivationShortcut != value)
+ {
+ _pastePlainSettings.Properties.ActivationShortcut = value;
+ OnPropertyChanged(nameof(ActivationShortcut));
+
+ _settingsUtils.SaveSettings(_pastePlainSettings.ToJsonString(), PastePlainSettings.ModuleName);
+ NotifySettingsChanged();
+ }
+ }
+ }
+
+ private void ScheduleSavingOfSettings()
+ {
+ lock (_delayedActionLock)
+ {
+ if (_delayedTimer.Enabled)
+ {
+ _delayedTimer.Stop();
+ }
+
+ _delayedTimer.Start();
+ }
+ }
+
+ private void DelayedTimer_Tick(object sender, EventArgs e)
+ {
+ lock (_delayedActionLock)
+ {
+ _delayedTimer.Stop();
+ NotifySettingsChanged();
+ }
+ }
+
+ private void NotifySettingsChanged()
+ {
+ // Using InvariantCulture as this is an IPC message
+ SendConfigMSG(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ "{{ \"powertoys\": {{ \"{0}\": {1} }} }}",
+ PastePlainSettings.ModuleName,
+ JsonSerializer.Serialize(_pastePlainSettings)));
+ }
+
+ public void RefreshEnabledState()
+ {
+ InitializeEnabledValue();
+ OnPropertyChanged(nameof(IsEnabled));
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ _delayedTimer.Dispose();
+ }
+
+ disposedValue = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/Views/PastePlainPage.xaml b/src/settings-ui/Settings.UI/Views/PastePlainPage.xaml
new file mode 100644
index 00000000000..c2d68dde7d8
--- /dev/null
+++ b/src/settings-ui/Settings.UI/Views/PastePlainPage.xaml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/settings-ui/Settings.UI/Views/PastePlainPage.xaml.cs b/src/settings-ui/Settings.UI/Views/PastePlainPage.xaml.cs
new file mode 100644
index 00000000000..9af9bf9621b
--- /dev/null
+++ b/src/settings-ui/Settings.UI/Views/PastePlainPage.xaml.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.PowerToys.Settings.UI.Helpers;
+using Microsoft.PowerToys.Settings.UI.Library;
+using Microsoft.PowerToys.Settings.UI.ViewModels;
+using Microsoft.UI.Xaml.Controls;
+
+namespace Microsoft.PowerToys.Settings.UI.Views
+{
+ public sealed partial class PastePlainPage : Page, IRefreshablePage
+ {
+ private PastePlainViewModel ViewModel { get; set; }
+
+ public PastePlainPage()
+ {
+ var settingsUtils = new SettingsUtils();
+ ViewModel = new PastePlainViewModel(
+ settingsUtils,
+ SettingsRepository.GetInstance(settingsUtils),
+ SettingsRepository.GetInstance(settingsUtils),
+ ShellPage.SendDefaultIPCMessage);
+ DataContext = ViewModel;
+ InitializeComponent();
+ }
+
+ public void RefreshEnabledState()
+ {
+ ViewModel.RefreshEnabledState();
+ }
+ }
+}
diff --git a/src/settings-ui/Settings.UI/Views/ShellPage.xaml b/src/settings-ui/Settings.UI/Views/ShellPage.xaml
index bfc102a8bad..268b25dc153 100644
--- a/src/settings-ui/Settings.UI/Views/ShellPage.xaml
+++ b/src/settings-ui/Settings.UI/Views/ShellPage.xaml
@@ -100,6 +100,11 @@
helpers:NavHelper.NavigateTo="views:MouseUtilsPage"
Icon="{ui:BitmapIcon Source=/Assets/FluentIcons/FluentIconsMouseUtils.png}" />
+
+