Remove old Advanced Paste module interface

This commit is contained in:
Noraa Junker
2025-12-01 23:28:02 +01:00
parent 1532dd5b38
commit 231f59c7a9
15 changed files with 0 additions and 1702 deletions

View File

@@ -499,8 +499,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MouseJumpUI", "src\modules\
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AdvancedPaste", "AdvancedPaste", "{9873BA05-4C41-4819-9283-CF45D795431B}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AdvancedPasteModuleInterface", "src\modules\AdvancedPaste\AdvancedPasteModuleInterface\AdvancedPasteModuleInterface.vcxproj", "{FC373B24-3293-453C-AAF5-CF2909DCEE6A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AllExperiments", "src\common\AllExperiments\AllExperiments.csproj", "{9CE59ED5-7087-4353-88EB-788038A73CEC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RegistryPreviewUILib", "src\modules\registrypreview\RegistryPreviewUILib\RegistryPreviewUILib.csproj", "{FD86C06A-FB54-4D5E-9831-1CDADF60D45F}"
@@ -2018,14 +2016,6 @@ Global
{D962A009-834F-4EEC-AABB-430DF8F98E39}.Release|ARM64.Build.0 = Release|ARM64
{D962A009-834F-4EEC-AABB-430DF8F98E39}.Release|x64.ActiveCfg = Release|x64
{D962A009-834F-4EEC-AABB-430DF8F98E39}.Release|x64.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}.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
{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
@@ -3191,7 +3181,6 @@ Global
{D5E42C63-57C5-4EF6-AECE-1E2FCA725B77} = {2C318EC3-BA86-4372-B1BC-DB0F33C208B2}
{D962A009-834F-4EEC-AABB-430DF8F98E39} = {322566EF-20DC-43A6-B9F8-616AF942579A}
{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}
{FD86C06A-FB54-4D5E-9831-1CDADF60D45F} = {929C1324-22E8-4412-A9A8-80E85F3985A5}
{697C6AF9-0A48-49A9-866C-67DA12384015} = {929C1324-22E8-4412-A9A8-80E85F3985A5}

View File

@@ -1,40 +0,0 @@
#include <windows.h>
#include "resource.h"
#include "../../../../common/version/version.h"
#define APSTUDIO_READONLY_SYMBOLS
#include "winres.h"
#undef APSTUDIO_READONLY_SYMBOLS
1 VERSIONINFO
FILEVERSION FILE_VERSION
PRODUCTVERSION PRODUCT_VERSION
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE VFT_DLL
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset
BEGIN
VALUE "CompanyName", COMPANY_NAME
VALUE "FileDescription", FILE_DESCRIPTION
VALUE "FileVersion", FILE_VERSION_STRING
VALUE "InternalName", INTERNAL_NAME
VALUE "LegalCopyright", COPYRIGHT_NOTE
VALUE "OriginalFilename", ORIGINAL_FILENAME
VALUE "ProductName", PRODUCT_NAME
VALUE "ProductVersion", PRODUCT_VERSION_STRING
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset
END
END

View File

@@ -1,8 +0,0 @@
#pragma once
#include <string>
namespace AdvancedPasteConstants
{
// Name of the powertoy module.
inline const std::wstring ModuleKey = L"AdvancedPaste";
}

View File

@@ -1,87 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" />
<Target Name="GenerateResourceFiles" BeforeTargets="PrepareForBuild">
<Exec Command="powershell -NonInteractive -executionpolicy Unrestricted ..\..\..\..\tools\build\convert-resx-to-rc.ps1 $(MSBuildThisFileDirectory) resource.base.h resource.h AdvancedPaste.base.rc AdvancedPaste.rc" />
</Target>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{FC373B24-3293-453C-AAF5-CF2909DCEE6A}</ProjectGuid>
<RootNamespace>AdvancedPaste</RootNamespace>
<ProjectName>AdvancedPasteModuleInterface</ProjectName>
<TargetName>PowerToys.AdvancedPasteModuleInterface</TargetName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<OutDir>..\..\..\..\$(Platform)\$(Configuration)\</OutDir>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>..\..\..\common\inc;..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="AdvancedPasteConstants.h" />
<ClInclude Include="AdvancedPasteProcessManager.h" />
<ClInclude Include="pch.h" />
<None Include="packages.config" />
<None Include="resource.base.h" />
<ClInclude Include="trace.h" />
<ClInclude Include="Generated Files\resource.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="AdvancedPasteProcessManager.cpp" />
<ClCompile Include="dllmain.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader Condition="'$(UsePrecompiledHeaders)' != 'false'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="trace.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\common\logger\logger.vcxproj">
<Project>{d9b8fc84-322a-4f9f-bbb9-20915c47ddfd}</Project>
</ProjectReference>
<ProjectReference Include="..\..\..\common\SettingsAPI\SettingsAPI.vcxproj">
<Project>{6955446d-23f7-4023-9bb3-8657f904af99}</Project>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="AdvancedPaste.base.rc" />
<ResourceCompile Include="Generated Files\AdvancedPaste.rc" />
</ItemGroup>
<ItemGroup>
<None Include="Resources.resx">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<Import Project="..\..\..\..\deps\spdlog.props" />
<ImportGroup Label="ExtensionTargets">
<Import Project="..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\..\packages\Microsoft.Windows.CppWinRT.2.0.240111.5\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

View File

@@ -1,68 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Generated Files">
<UniqueIdentifier>{875a08c6-f610-4667-bd0f-80171ed96072}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Generated Files\resource.h">
<Filter>Generated Files</Filter>
</ClInclude>
<ClInclude Include="AdvancedPasteConstants.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="trace.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="AdvancedPasteProcessManager.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="dllmain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="pch.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="trace.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="AdvancedPasteProcessManager.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="resource.base.h">
<Filter>Header Files</Filter>
</None>
<None Include="Resources.resx">
<Filter>Resource Files</Filter>
</None>
<None Include="packages.config" />
<None Include="AdvancedPaste.base.rc">
<Filter>Resource Files</Filter>
</None>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Generated Files\AdvancedPaste.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
</Project>

View File

@@ -1,267 +0,0 @@
#include "pch.h"
#include "AdvancedPasteProcessManager.h"
#include <common/logger/logger.h>
#include <common/utils/winapi_error.h>
#include <common/interop/shared_constants.h>
#include <atlstr.h>
namespace
{
std::optional<std::wstring> get_pipe_name(const std::wstring& prefix)
{
UUID temp_uuid;
wchar_t* uuid_chars = nullptr;
if (UuidCreate(&temp_uuid) == RPC_S_UUID_NO_ADDRESS)
{
const auto val = get_last_error_message(GetLastError());
Logger::error(L"UuidCreate cannot create guid. {}", val.has_value() ? val.value() : L"");
return std::nullopt;
}
else if (UuidToString(&temp_uuid, reinterpret_cast<RPC_WSTR*>(&uuid_chars)) != RPC_S_OK)
{
const auto val = get_last_error_message(GetLastError());
Logger::error(L"UuidToString cannot convert to string. {}", val.has_value() ? val.value() : L"");
return std::nullopt;
}
const auto pipe_name = std::format(L"{}{}", prefix, std::wstring(uuid_chars));
RpcStringFree(reinterpret_cast<RPC_WSTR*>(&uuid_chars));
return pipe_name;
}
}
void AdvancedPasteProcessManager::start()
{
m_enabled = true;
submit_task([this]() { refresh(); });
}
void AdvancedPasteProcessManager::stop()
{
m_enabled = false;
submit_task([this]() { refresh(); });
}
void AdvancedPasteProcessManager::send_message(const std::wstring& message_type, const std::wstring& message_arg)
{
submit_task([this, message_type, message_arg] {
send_named_pipe_message(message_type, message_arg);
});
}
void AdvancedPasteProcessManager::bring_to_front()
{
submit_task([this] {
if (!is_process_running())
{
return;
}
const auto enum_windows = [](HWND hwnd, LPARAM param) -> BOOL {
const auto process_handle = reinterpret_cast<HANDLE>(param);
DWORD window_process_id = 0;
GetWindowThreadProcessId(hwnd, &window_process_id);
if (GetProcessId(process_handle) == window_process_id)
{
SetForegroundWindow(hwnd);
return FALSE;
}
return TRUE;
};
EnumWindows(enum_windows, reinterpret_cast<LPARAM>(m_hProcess));
});
}
void AdvancedPasteProcessManager::submit_task(std::function<void()> task)
{
m_thread_executor.submit(OnThreadExecutor::task_t{ task });
}
bool AdvancedPasteProcessManager::is_process_running() const
{
return m_hProcess != 0 && WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT;
}
void AdvancedPasteProcessManager::terminate_process()
{
if (m_hProcess != 0)
{
TerminateProcess(m_hProcess, 1);
CloseHandle(m_hProcess);
m_hProcess = 0;
}
}
HRESULT AdvancedPasteProcessManager::start_process(const std::wstring& pipe_name)
{
const unsigned long powertoys_pid = GetCurrentProcessId();
const auto executable_args = std::format(L"{} {}", std::to_wstring(powertoys_pid), pipe_name);
SHELLEXECUTEINFOW sei{ sizeof(sei) };
sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI };
sei.lpFile = L"WinUI3Apps\\PowerToys.AdvancedPaste.exe";
sei.nShow = SW_SHOWNORMAL;
sei.lpParameters = executable_args.data();
if (ShellExecuteExW(&sei))
{
Logger::trace("Successfully started Advanced Paste process");
terminate_process();
m_hProcess = sei.hProcess;
return S_OK;
}
else
{
Logger::error(L"Advanced Paste process failed to start. {}", get_last_error_or_default(GetLastError()));
return E_FAIL;
}
}
HRESULT AdvancedPasteProcessManager::start_named_pipe_server(const std::wstring& pipe_name)
{
m_write_pipe = nullptr;
const constexpr DWORD BUFSIZE = 4096 * 4;
const auto full_pipe_name = std::format(L"\\\\.\\pipe\\{}", pipe_name);
const auto hPipe = CreateNamedPipe(
full_pipe_name.c_str(), // pipe name
PIPE_ACCESS_OUTBOUND | // write access
FILE_FLAG_OVERLAPPED, // overlapped mode
PIPE_TYPE_MESSAGE | // message type pipe
PIPE_READMODE_MESSAGE | // message-read mode
PIPE_WAIT, // blocking mode
1, // max. instances
BUFSIZE, // output buffer size
0, // input buffer size
0, // client time-out
NULL); // default security attribute
if (hPipe == NULL || hPipe == INVALID_HANDLE_VALUE)
{
Logger::error(L"Error creating handle for named pipe");
return E_FAIL;
}
// Create overlapped event to wait for client to connect to pipe.
OVERLAPPED overlapped = { 0 };
overlapped.hEvent = CreateEvent(nullptr, true, false, nullptr);
if (!overlapped.hEvent)
{
Logger::error(L"Error creating overlapped event for named pipe");
CloseHandle(hPipe);
return E_FAIL;
}
const auto clean_up_and_fail = [&]() {
CloseHandle(overlapped.hEvent);
CloseHandle(hPipe);
return E_FAIL;
};
if (!ConnectNamedPipe(hPipe, &overlapped))
{
const auto lastError = GetLastError();
if (lastError != ERROR_IO_PENDING && lastError != ERROR_PIPE_CONNECTED)
{
Logger::error(L"Error connecting to named pipe");
return clean_up_and_fail();
}
}
// Wait for client.
const constexpr DWORD client_timeout_millis = 5000;
switch (WaitForSingleObject(overlapped.hEvent, client_timeout_millis))
{
case WAIT_OBJECT_0:
{
DWORD bytes_transferred = 0;
if (GetOverlappedResult(hPipe, &overlapped, &bytes_transferred, FALSE))
{
CloseHandle(overlapped.hEvent);
m_write_pipe = std::make_unique<CAtlFile>(hPipe);
Logger::trace(L"Advanced Paste successfully connected to named pipe");
return S_OK;
}
else
{
Logger::error(L"Error waiting for Advanced Paste to connect to named pipe");
return clean_up_and_fail();
}
}
case WAIT_TIMEOUT:
case WAIT_FAILED:
default:
Logger::error(L"Error waiting for Advanced Paste to connect to named pipe");
return clean_up_and_fail();
}
}
void AdvancedPasteProcessManager::refresh()
{
if (m_enabled == is_process_running())
{
return;
}
if (m_enabled)
{
Logger::trace(L"Starting Advanced Paste process");
const auto pipe_name = get_pipe_name(L"powertoys_advanced_paste_");
if (!pipe_name)
{
return;
}
if (start_process(pipe_name.value()) != S_OK)
{
return;
}
if (start_named_pipe_server(pipe_name.value()) != S_OK)
{
Logger::error(L"Named pipe initialization failed; terminating Advanced Paste process");
terminate_process();
}
}
else
{
Logger::trace(L"Exiting Advanced Paste process");
send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_TERMINATE_APP_MESSAGE);
WaitForSingleObject(m_hProcess, 5000);
if (is_process_running())
{
Logger::error(L"Advanced Paste process failed to gracefully exit; terminating");
}
else
{
Logger::trace(L"Advanced Paste process successfully exited");
}
terminate_process();
}
}
void AdvancedPasteProcessManager::send_named_pipe_message(const std::wstring& message_type, const std::wstring& message_arg)
{
if (m_write_pipe)
{
const auto message = message_arg.empty() ? std::format(L"{}\r\n", message_type) : std::format(L"{} {}\r\n", message_type, message_arg);
const CString file_name(message.c_str());
m_write_pipe->Write(file_name, file_name.GetLength() * sizeof(TCHAR));
}
}

View File

@@ -1,36 +0,0 @@
#pragma once
#include "pch.h"
#include <common/utils/OnThreadExecutor.h>
#include <atlfile.h>
#include <string>
#include <atomic>
#include <memory>
#include <functional>
#include <optional>
class AdvancedPasteProcessManager
{
public:
AdvancedPasteProcessManager() = default;
AdvancedPasteProcessManager(const AdvancedPasteProcessManager&) = delete;
AdvancedPasteProcessManager& operator=(const AdvancedPasteProcessManager&) = delete;
void start();
void stop();
void send_message(const std::wstring& message_type, const std::wstring& message_arg = L"");
void bring_to_front();
private:
void submit_task(std::function<void()> task);
bool is_process_running() const;
void terminate_process();
HRESULT start_process(const std::wstring& pipe_name);
HRESULT start_named_pipe_server(const std::wstring& pipe_name);
void refresh();
void send_named_pipe_message(const std::wstring& message_type, const std::wstring& message_arg = L"");
OnThreadExecutor m_thread_executor; // all internal operations are done on background thread with task queue
std::atomic<bool> m_enabled = false; // written on main thread, read on background thread
HANDLE m_hProcess = 0;
std::unique_ptr<CAtlFile> m_write_pipe;
};

View File

@@ -1,129 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root"
xmlns=""
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Advanced_Paste_Name" xml:space="preserve">
<value>Advanced Paste</value>
</data>
<data name="Advanced_Paste_Settings_Desc" xml:space="preserve">
<value>An AI powered tool to put your clipboard content into any format you need, focused towards developer workflows.</value>
</data>
</root>

View File

@@ -1,909 +0,0 @@
// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include "AdvancedPasteConstants.h"
#include "AdvancedPasteProcessManager.h"
#include <interface/powertoy_module_interface.h>
#include "trace.h"
#include "Generated Files/resource.h"
#include <common/logger/logger.h>
#include <common/SettingsAPI/settings_helpers.h>
#include <common/SettingsAPI/settings_objects.h>
#include <common/utils/resources.h>
#include <common/interop/shared_constants.h>
#include <common/utils/logger_helper.h>
#include <common/utils/winapi_error.h>
#include <common/utils/gpo.h>
#include <algorithm>
#include <cwctype>
#include <vector>
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_CUSTOM_ACTIONS[] = L"custom-actions";
const wchar_t JSON_KEY_ADDITIONAL_ACTIONS[] = L"additional-actions";
const wchar_t JSON_KEY_SHORTCUT[] = L"shortcut";
const wchar_t JSON_KEY_IS_SHOWN[] = L"isShown";
const wchar_t JSON_KEY_ID[] = L"id";
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_PASTE_AS_PLAIN_HOTKEY[] = L"paste-as-plain-hotkey";
const wchar_t JSON_KEY_ADVANCED_PASTE_UI_HOTKEY[] = L"advanced-paste-ui-hotkey";
const wchar_t JSON_KEY_PASTE_AS_MARKDOWN_HOTKEY[] = L"paste-as-markdown-hotkey";
const wchar_t JSON_KEY_PASTE_AS_JSON_HOTKEY[] = L"paste-as-json-hotkey";
const wchar_t JSON_KEY_IS_AI_ENABLED[] = L"IsAIEnabled";
const wchar_t JSON_KEY_IS_OPEN_AI_ENABLED[] = L"IsOpenAIEnabled";
const wchar_t JSON_KEY_SHOW_CUSTOM_PREVIEW[] = L"ShowCustomPreview";
const wchar_t JSON_KEY_PASTE_AI_CONFIGURATION[] = L"paste-ai-configuration";
const wchar_t JSON_KEY_PROVIDERS[] = L"providers";
const wchar_t JSON_KEY_SERVICE_TYPE[] = L"service-type";
const wchar_t JSON_KEY_ENABLE_ADVANCED_AI[] = L"enable-advanced-ai";
const wchar_t JSON_KEY_VALUE[] = L"value";
}
class AdvancedPaste : public PowertoyModuleIface
{
private:
AdvancedPasteProcessManager m_process_manager;
bool m_enabled = false;
std::wstring app_name;
//contains the non localized key of the powertoy
std::wstring app_key;
static const constexpr int NUM_DEFAULT_HOTKEYS = 4;
Hotkey m_paste_as_plain_hotkey = { .win = true, .ctrl = true, .shift = false, .alt = true, .key = 'V' };
Hotkey m_advanced_paste_ui_hotkey = { .win = true, .ctrl = false, .shift = true, .alt = false, .key = 'V' };
Hotkey m_paste_as_markdown_hotkey{};
Hotkey m_paste_as_json_hotkey{};
template<class Id>
struct ActionData
{
Id id;
Hotkey hotkey;
};
using AdditionalAction = ActionData<std::wstring>;
std::vector<AdditionalAction> m_additional_actions;
using CustomAction = ActionData<int>;
std::vector<CustomAction> m_custom_actions;
bool m_is_ai_enabled = false;
bool m_is_advanced_ai_enabled = false;
bool m_preview_custom_format_output = true;
Hotkey parse_single_hotkey(const wchar_t* keyName, const winrt::Windows::Data::Json::JsonObject& settingsObject)
{
try
{
const auto jsonHotkeyObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).GetNamedObject(keyName);
return parse_single_hotkey(jsonHotkeyObject);
}
catch (...)
{
Logger::error("Failed to initialize AdvancedPaste shortcut from settings. Value will keep unchanged.");
}
return {};
}
static Hotkey parse_single_hotkey(const winrt::Windows::Data::Json::JsonObject& jsonHotkeyObject, bool isShown = true)
{
try
{
Hotkey hotkey;
hotkey.win = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_WIN);
hotkey.alt = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_ALT);
hotkey.shift = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_SHIFT);
hotkey.ctrl = jsonHotkeyObject.GetNamedBoolean(JSON_KEY_CTRL);
hotkey.key = static_cast<unsigned char>(jsonHotkeyObject.GetNamedNumber(JSON_KEY_CODE));
hotkey.isShown = isShown;
return hotkey;
}
catch (...)
{
Logger::error("Failed to initialize AdvancedPaste shortcut from settings. Value will keep unchanged.");
}
return {};
}
static json::JsonObject to_json_object(const Hotkey& hotkey)
{
json::JsonObject jsonObject;
jsonObject.SetNamedValue(JSON_KEY_WIN, json::value(hotkey.win));
jsonObject.SetNamedValue(JSON_KEY_ALT, json::value(hotkey.alt));
jsonObject.SetNamedValue(JSON_KEY_SHIFT, json::value(hotkey.shift));
jsonObject.SetNamedValue(JSON_KEY_CTRL, json::value(hotkey.ctrl));
jsonObject.SetNamedValue(JSON_KEY_CODE, json::value(hotkey.key));
return jsonObject;
}
bool is_ai_enabled()
{
return gpo_policy_enabled_configuration() != powertoys_gpo::gpo_rule_configured_disabled &&
powertoys_gpo::getAllowedAdvancedPasteOnlineAIModelsValue() != powertoys_gpo::gpo_rule_configured_disabled &&
m_is_ai_enabled;
}
static std::wstring kebab_to_pascal_case(const std::wstring& kebab_str)
{
std::wstring result;
bool capitalize_next = true;
for (const auto ch : kebab_str)
{
if (ch == L'-')
{
capitalize_next = true;
}
else
{
if (capitalize_next)
{
result += std::towupper(ch);
capitalize_next = false;
}
else
{
result += ch;
}
}
}
return result;
}
static std::wstring to_lower_case(const std::wstring& value)
{
std::wstring result = value;
std::transform(result.begin(), result.end(), result.begin(), [](wchar_t ch) { return std::towlower(ch); });
return result;
}
bool migrate_data_and_remove_data_file(Hotkey& old_paste_as_plain_hotkey)
{
const wchar_t OLD_JSON_KEY_ACTIVATION_SHORTCUT[] = L"ActivationShortcut";
const wchar_t OLD_MODULE_KEY[] = L"PastePlain";
try
{
const std::wstring save_file_location = PTSettingsHelper::get_module_save_file_location(OLD_MODULE_KEY);
if (std::filesystem::exists(save_file_location))
{
PowerToysSettings::PowerToyValues settings =
PowerToysSettings::PowerToyValues::load_from_settings_file(OLD_MODULE_KEY);
auto settingsObject = settings.get_raw_json();
if (settingsObject.GetView().Size())
{
old_paste_as_plain_hotkey = parse_single_hotkey(OLD_JSON_KEY_ACTIVATION_SHORTCUT, settingsObject);
std::filesystem::remove(save_file_location);
return true;
}
}
}
catch (std::exception)
{
}
return false;
}
void process_additional_action(const winrt::hstring& actionName, const winrt::Windows::Data::Json::IJsonValue& actionValue, bool actionsGroupIsShown = true)
{
bool actionIsShown = true;
if (actionValue.ValueType() != winrt::Windows::Data::Json::JsonValueType::Object)
{
return;
}
const auto action = actionValue.GetObjectW();
if (!action.GetNamedBoolean(JSON_KEY_IS_SHOWN, false) || !actionsGroupIsShown)
{
actionIsShown = false;
}
if (action.HasKey(JSON_KEY_SHORTCUT))
{
const AdditionalAction additionalAction
{
actionName.c_str(),
parse_single_hotkey(action.GetNamedObject(JSON_KEY_SHORTCUT), actionIsShown)
};
m_additional_actions.push_back(additionalAction);
}
else
{
for (const auto& [subActionName, subAction] : action)
{
process_additional_action(subActionName, subAction, actionIsShown);
}
}
}
bool has_advanced_ai_provider(const winrt::Windows::Data::Json::JsonObject& propertiesObject)
{
if (!propertiesObject.HasKey(JSON_KEY_PASTE_AI_CONFIGURATION))
{
return false;
}
const auto configValue = propertiesObject.GetNamedValue(JSON_KEY_PASTE_AI_CONFIGURATION);
if (configValue.ValueType() != winrt::Windows::Data::Json::JsonValueType::Object)
{
return false;
}
const auto configObject = configValue.GetObjectW();
if (!configObject.HasKey(JSON_KEY_PROVIDERS))
{
return false;
}
const auto providersValue = configObject.GetNamedValue(JSON_KEY_PROVIDERS);
if (providersValue.ValueType() != winrt::Windows::Data::Json::JsonValueType::Array)
{
return false;
}
const auto providers = providersValue.GetArray();
for (const auto providerValue : providers)
{
if (providerValue.ValueType() != winrt::Windows::Data::Json::JsonValueType::Object)
{
continue;
}
const auto providerObject = providerValue.GetObjectW();
if (!providerObject.GetNamedBoolean(JSON_KEY_ENABLE_ADVANCED_AI, false))
{
continue;
}
if (!providerObject.HasKey(JSON_KEY_SERVICE_TYPE))
{
continue;
}
const std::wstring serviceType = providerObject.GetNamedString(JSON_KEY_SERVICE_TYPE, L"").c_str();
const auto normalizedServiceType = to_lower_case(serviceType);
if (normalizedServiceType == L"openai" || normalizedServiceType == L"azureopenai")
{
return true;
}
}
return false;
}
void read_settings(PowerToysSettings::PowerToyValues& settings)
{
const auto settingsObject = settings.get_raw_json();
// Migrate Paste As Plain text shortcut
Hotkey old_paste_as_plain_hotkey;
bool old_data_migrated = migrate_data_and_remove_data_file(old_paste_as_plain_hotkey);
if (old_data_migrated)
{
m_paste_as_plain_hotkey = old_paste_as_plain_hotkey;
// override settings file
const auto new_hotkey_value = to_json_object(old_paste_as_plain_hotkey);
if (!settingsObject.HasKey(JSON_KEY_PROPERTIES))
{
settingsObject.SetNamedValue(JSON_KEY_PROPERTIES, json::JsonObject{});
}
settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).SetNamedValue(JSON_KEY_PASTE_AS_PLAIN_HOTKEY, new_hotkey_value);
const auto ui_hotkey = to_json_object(m_advanced_paste_ui_hotkey);
settingsObject.GetNamedObject(JSON_KEY_PROPERTIES).SetNamedValue(JSON_KEY_ADVANCED_PASTE_UI_HOTKEY, ui_hotkey);
settings.save_to_settings_file();
}
else
{
if (settingsObject.GetView().Size())
{
const std::array<std::pair<Hotkey*, LPCWSTR>, NUM_DEFAULT_HOTKEYS> defaultHotkeys{
{ { &m_paste_as_plain_hotkey, JSON_KEY_PASTE_AS_PLAIN_HOTKEY },
{ &m_advanced_paste_ui_hotkey, JSON_KEY_ADVANCED_PASTE_UI_HOTKEY },
{ &m_paste_as_markdown_hotkey, JSON_KEY_PASTE_AS_MARKDOWN_HOTKEY },
{ &m_paste_as_json_hotkey, JSON_KEY_PASTE_AS_JSON_HOTKEY } }
};
for (auto& [hotkey, keyName] : defaultHotkeys)
{
*hotkey = parse_single_hotkey(keyName, settingsObject);
}
m_additional_actions.clear();
m_custom_actions.clear();
if (settingsObject.HasKey(JSON_KEY_PROPERTIES))
{
const auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
if (propertiesObject.HasKey(JSON_KEY_ADDITIONAL_ACTIONS))
{
const auto additionalActions = propertiesObject.GetNamedObject(JSON_KEY_ADDITIONAL_ACTIONS);
// Define the expected order to ensure consistent hotkey ID assignment
const std::vector<winrt::hstring> expectedOrder = {
L"image-to-text",
L"paste-as-file",
L"transcode"
};
// Process actions in the predefined order
for (auto& actionKey : expectedOrder)
{
if (additionalActions.HasKey(actionKey))
{
const auto actionValue = additionalActions.GetNamedValue(actionKey);
process_additional_action(actionKey, actionValue);
}
}
}
if (propertiesObject.HasKey(JSON_KEY_CUSTOM_ACTIONS))
{
const auto customActions = propertiesObject.GetNamedObject(JSON_KEY_CUSTOM_ACTIONS).GetNamedArray(JSON_KEY_VALUE);
if (customActions.Size() > 0 && is_ai_enabled())
{
for (const auto& customAction : customActions)
{
const auto object = customAction.GetObjectW();
bool actionIsShown = object.GetNamedBoolean(JSON_KEY_IS_SHOWN, false);
const CustomAction customActionData{
static_cast<int>(object.GetNamedNumber(JSON_KEY_ID)),
parse_single_hotkey(object.GetNamedObject(JSON_KEY_SHORTCUT), actionIsShown)
};
m_custom_actions.push_back(customActionData);
}
}
}
}
}
}
if (settingsObject.GetView().Size())
{
const auto propertiesObject = settingsObject.GetNamedObject(JSON_KEY_PROPERTIES);
m_is_advanced_ai_enabled = has_advanced_ai_provider(propertiesObject);
if (propertiesObject.HasKey(JSON_KEY_IS_AI_ENABLED))
{
m_is_ai_enabled = propertiesObject.GetNamedObject(JSON_KEY_IS_AI_ENABLED).GetNamedBoolean(JSON_KEY_VALUE, false);
}
else if (propertiesObject.HasKey(JSON_KEY_IS_OPEN_AI_ENABLED))
{
m_is_ai_enabled = propertiesObject.GetNamedObject(JSON_KEY_IS_OPEN_AI_ENABLED).GetNamedBoolean(JSON_KEY_VALUE, false);
}
else
{
m_is_ai_enabled = false;
}
if (propertiesObject.HasKey(JSON_KEY_SHOW_CUSTOM_PREVIEW))
{
m_preview_custom_format_output = propertiesObject.GetNamedObject(JSON_KEY_SHOW_CUSTOM_PREVIEW).GetNamedBoolean(JSON_KEY_VALUE);
}
}
}
// 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());
read_settings(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<INPUT>& inputs, short modifier)
{
// Most significant bit is set if key is down
if ((GetAsyncKeyState(static_cast<int>(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_inject_modifier_key_restore(std::vector<INPUT>& inputs, short modifier)
{
// Most significant bit is set if key is down
if ((GetAsyncKeyState(static_cast<int>(modifier)) & 0x8000) != 0)
{
INPUT input_event = {};
input_event.type = INPUT_KEYBOARD;
input_event.ki.wVk = modifier;
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::AdvancedPaste_Error(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::AdvancedPaste_Error(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"read.GetClipboardData");
CloseClipboard();
return;
}
wchar_t* pch_data = static_cast<wchar_t*>(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::AdvancedPaste_Error(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/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::AdvancedPaste_Error(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::AdvancedPaste_Error(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::AdvancedPaste_Error(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"write.GlobalAlloc");
CloseClipboard();
return;
}
wchar_t* pch_data = static_cast<wchar_t*>(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::AdvancedPaste_Error(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::AdvancedPaste_Error(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<INPUT> 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
// Avoid triggering detection by the centralized keyboard hook. Allows using Control+V as activation.
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
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;
// Avoid triggering detection by the centralized keyboard hook. Allows using Control+V as activation.
input_event.ki.dwExtraInfo = CENTRALIZED_KEYBOARD_HOOK_DONT_TRIGGER_FLAG;
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);
}
try_inject_modifier_key_restore(inputs, VK_LCONTROL);
try_inject_modifier_key_restore(inputs, VK_RCONTROL);
try_inject_modifier_key_restore(inputs, VK_LWIN);
try_inject_modifier_key_restore(inputs, VK_RWIN);
try_inject_modifier_key_restore(inputs, VK_LSHIFT);
try_inject_modifier_key_restore(inputs, VK_RSHIFT);
try_inject_modifier_key_restore(inputs, VK_LMENU);
try_inject_modifier_key_restore(inputs, VK_RMENU);
// After restoring the modifier keys send a dummy key to prevent Start Menu from activating
INPUT dummyEvent = {};
dummyEvent.type = INPUT_KEYBOARD;
dummyEvent.ki.wVk = 0xFF;
dummyEvent.ki.dwFlags = KEYEVENTF_KEYUP;
inputs.push_back(dummyEvent);
auto uSent = SendInput(static_cast<UINT>(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::AdvancedPaste_Error(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"input.SendInput");
return;
}
// Clear kb state and send Ctrl+V end
}
}
public:
AdvancedPaste()
{
app_name = GET_RESOURCE_STRING(IDS_ADVANCED_PASTE_NAME);
app_key = AdvancedPasteConstants::ModuleKey;
LoggerHelpers::init_logger(app_key, L"ModuleInterface", "AdvancedPaste");
init_settings();
}
~AdvancedPaste()
{
if (m_enabled)
{
}
m_enabled = false;
}
// Destroy the powertoy and free memory
virtual void destroy() override
{
Disable(false);
Logger::trace("AdvancedPaste::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::getConfiguredAdvancedPasteEnabledValue();
}
virtual bool get_config(wchar_t* buffer, int* buffer_size) override
{
HINSTANCE hinstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
// Create a Settings object.
PowerToysSettings::Settings settings(hinstance, get_name());
settings.set_description(GET_RESOURCE_STRING(IDS_ADVANCED_PASTE_SETTINGS_DESC));
settings.set_overview_link(L"https://aka.ms/PowerToysOverview_AdvancedPaste");
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());
read_settings(values);
std::unordered_map<std::wstring, Hotkey> additionalActionMap;
for (const auto& action : m_additional_actions)
{
additionalActionMap[kebab_to_pascal_case(action.id)] = action.hotkey;
}
// order of args matter
Trace::AdvancedPaste_SettingsTelemetry(m_paste_as_plain_hotkey,
m_advanced_paste_ui_hotkey,
m_paste_as_markdown_hotkey,
m_paste_as_json_hotkey,
m_is_advanced_ai_enabled,
m_preview_custom_format_output,
additionalActionMap);
// 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("AdvancedPaste::enable()");
Trace::AdvancedPaste_Enable(true);
m_enabled = true;
m_process_manager.start();
};
void Disable(bool traceEvent)
{
if (m_enabled)
{
m_process_manager.stop();
if (traceEvent)
{
Trace::AdvancedPaste_Enable(false);
}
}
m_enabled = false;
}
virtual void disable()
{
Logger::trace("AdvancedPaste::disable()");
Disable(true);
}
virtual bool on_hotkey(size_t hotkeyId) override
{
Logger::trace(L"AdvancedPaste hotkey pressed");
if (m_enabled)
{
m_process_manager.start();
// hotkeyId in same order as set by get_hotkeys
if (hotkeyId == 0)
{ // m_paste_as_plain_hotkey
Logger::trace(L"Paste as plain text 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::AdvancedPaste_Invoked(L"PastePlainTextDirect");
return true;
}
if (hotkeyId == 1)
{ // m_advanced_paste_ui_hotkey
Logger::trace(L"Setting start up event");
m_process_manager.bring_to_front();
m_process_manager.send_message(CommonSharedConstants::ADVANCED_PASTE_SHOW_UI_MESSAGE);
Trace::AdvancedPaste_Invoked(L"AdvancedPasteUI");
return true;
}
if (hotkeyId == 2)
{ // m_paste_as_markdown_hotkey
Logger::trace(L"Starting paste as markdown directly");
m_process_manager.send_message(CommonSharedConstants::ADVANCED_PASTE_MARKDOWN_MESSAGE);
Trace::AdvancedPaste_Invoked(L"MarkdownDirect");
return true;
}
if (hotkeyId == 3)
{ // m_paste_as_json_hotkey
Logger::trace(L"Starting paste as json directly");
m_process_manager.send_message(CommonSharedConstants::ADVANCED_PASTE_JSON_MESSAGE);
Trace::AdvancedPaste_Invoked(L"JsonDirect");
return true;
}
const auto additional_action_index = hotkeyId - NUM_DEFAULT_HOTKEYS;
if (additional_action_index < m_additional_actions.size())
{
const auto& id = m_additional_actions.at(additional_action_index).id;
Logger::trace(L"Starting additional action id={}", id);
Trace::AdvancedPaste_Invoked(std::format(L"{}Direct", kebab_to_pascal_case(id)));
m_process_manager.send_message(CommonSharedConstants::ADVANCED_PASTE_ADDITIONAL_ACTION_MESSAGE, id);
return true;
}
const auto custom_action_index = additional_action_index - m_additional_actions.size();
if (custom_action_index < m_custom_actions.size())
{
const auto id = m_custom_actions.at(custom_action_index).id;
Logger::trace(L"Starting custom action id={}", id);
m_process_manager.send_message(CommonSharedConstants::ADVANCED_PASTE_CUSTOM_ACTION_MESSAGE, std::to_wstring(id));
Trace::AdvancedPaste_Invoked(L"CustomActionDirect");
return true;
}
}
return false;
}
virtual size_t get_hotkeys(Hotkey* hotkeys, size_t buffer_size) override
{
const size_t num_hotkeys = NUM_DEFAULT_HOTKEYS + m_additional_actions.size() + m_custom_actions.size();
if (hotkeys && buffer_size >= num_hotkeys)
{
const std::array default_hotkeys = { m_paste_as_plain_hotkey,
m_advanced_paste_ui_hotkey,
m_paste_as_markdown_hotkey,
m_paste_as_json_hotkey };
std::copy(default_hotkeys.begin(), default_hotkeys.end(), hotkeys);
const auto get_action_hotkey = [](const auto& action) { return action.hotkey; };
std::transform(m_additional_actions.begin(), m_additional_actions.end(), hotkeys + NUM_DEFAULT_HOTKEYS, get_action_hotkey);
std::transform(m_custom_actions.begin(), m_custom_actions.end(), hotkeys + NUM_DEFAULT_HOTKEYS + m_additional_actions.size(), get_action_hotkey);
}
return num_hotkeys;
}
virtual bool is_enabled() override
{
return m_enabled;
}
};
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()
{
return new AdvancedPaste();
}

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.240111.5" targetFramework="native" />
</packages>

View File

@@ -1,2 +0,0 @@
#include "pch.h"

View File

@@ -1,7 +0,0 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <shellapi.h>
#include <Shlwapi.h>
#include <filesystem>

View File

@@ -1,13 +0,0 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by PowerOCR.rc
//////////////////////////////
// Non-localizable
#define FILE_DESCRIPTION "PowerToys AdvancedPaste"
#define INTERNAL_NAME "PowerToys.AdvancedPasteModuleInterface"
#define ORIGINAL_FILENAME "PowerToys.AdvancedPasteModuleInterface.dll"
// Non-localizable
//////////////////////////////

View File

@@ -1,95 +0,0 @@
#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());
// Log if the user has AdvancedPaste enabled or disabled
void Trace::AdvancedPaste_Enable(const bool enabled) noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"AdvancedPaste_EnableAdvancedPaste",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingBoolean(enabled, "Enabled"));
}
// Log if the user has invoked AdvancedPaste
void Trace::AdvancedPaste_Invoked(std::wstring mode) noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"AdvancedPaste_InvokeAdvancedPaste",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingValue(mode.c_str(), "Mode"));
}
// Log if an error occurs in AdvancedPaste
void Trace::AdvancedPaste_Error(const DWORD errorCode, std::wstring errorMessage, std::wstring methodName) noexcept
{
TraceLoggingWriteWrapper(
g_hProvider,
"AdvancedPaste_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::AdvancedPaste_SettingsTelemetry(const PowertoyModuleIface::Hotkey& pastePlainHotkey,
const PowertoyModuleIface::Hotkey& advancedPasteUIHotkey,
const PowertoyModuleIface::Hotkey& pasteMarkdownHotkey,
const PowertoyModuleIface::Hotkey& pasteJsonHotkey,
const bool is_advanced_ai_enabled,
const bool preview_custom_format_output,
const std::unordered_map<std::wstring, PowertoyModuleIface::Hotkey>& additionalActionsHotkeys) noexcept
{
const auto getHotKeyStr = [](const PowertoyModuleIface::Hotkey& hotKey)
{
return 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);
};
std::vector<std::wstring> hotkeyStrs;
const auto getHotkeyCStr = [&](const PowertoyModuleIface::Hotkey& hotkey)
{
hotkeyStrs.push_back(getHotKeyStr(hotkey)); // Probably unnecessary, but offers protection against the macro TraceLoggingWideString expanding to something that would invalidate the pointer
return hotkeyStrs.back().c_str();
};
const auto getAdditionalActionHotkeyCStr = [&](const std::wstring& name)
{
const auto it = additionalActionsHotkeys.find(name);
return it != additionalActionsHotkeys.end() ? getHotkeyCStr(it->second) : L"";
};
TraceLoggingWriteWrapper(
g_hProvider,
"AdvancedPaste_Settings",
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
TraceLoggingKeyword(PROJECT_KEYWORD_MEASURE),
TraceLoggingWideString(getHotkeyCStr(pastePlainHotkey), "PastePlainHotkey"),
TraceLoggingWideString(getHotkeyCStr(advancedPasteUIHotkey), "AdvancedPasteUIHotkey"),
TraceLoggingWideString(getHotkeyCStr(pasteMarkdownHotkey), "PasteMarkdownHotkey"),
TraceLoggingWideString(getHotkeyCStr(pasteJsonHotkey), "PasteJsonHotkey"),
TraceLoggingBoolean(is_advanced_ai_enabled, "IsAdvancedAIEnabled"),
TraceLoggingBoolean(preview_custom_format_output, "ShowCustomPreview"),
TraceLoggingWideString(getAdditionalActionHotkeyCStr(L"ImageToText"), "ImageToTextHotkey"),
TraceLoggingWideString(getAdditionalActionHotkeyCStr(L"PasteAsTxtFile"), "PasteAsTxtFileHotkey"),
TraceLoggingWideString(getAdditionalActionHotkeyCStr(L"PasteAsPngFile"), "PasteAsPngFileHotkey"),
TraceLoggingWideString(getAdditionalActionHotkeyCStr(L"PasteAsHtmlFile"), "PasteAsHtmlFileHotkey"),
TraceLoggingWideString(getAdditionalActionHotkeyCStr(L"TranscodeToMp3"), "TranscodeToMp3Hotkey"),
TraceLoggingWideString(getAdditionalActionHotkeyCStr(L"TranscodeToMp4"), "TranscodeToMp4Hotkey")
);
}

View File

@@ -1,26 +0,0 @@
#pragma once
#include <common/Telemetry/TraceBase.h>
#include <interface/powertoy_module_interface.h>
#include <unordered_map>
class Trace : public telemetry::TraceBase
{
public:
// Log if the user has AdvancedPaste enabled or disabled
static void AdvancedPaste_Enable(const bool enabled) noexcept;
// Log if the user has invoked AdvancedPaste
static void AdvancedPaste_Invoked(std::wstring mode) noexcept;
// Log if an error occurs in AdvancedPaste
static void Trace::AdvancedPaste_Error(const DWORD errorCode, std::wstring errorMessage, std::wstring methodName) noexcept;
// Event to send settings telemetry.
static void Trace::AdvancedPaste_SettingsTelemetry(const PowertoyModuleIface::Hotkey& pastePlainHotkey,
const PowertoyModuleIface::Hotkey& advancedPasteUIHotkey,
const PowertoyModuleIface::Hotkey& pasteMarkdownHotkey,
const PowertoyModuleIface::Hotkey& pasteJsonHotkey,
const bool is_advanced_ai_enabled,
const bool preview_custom_format_output,
const std::unordered_map<std::wstring, PowertoyModuleIface::Hotkey>& additionalActionsHotkeys) noexcept;
};