diff --git a/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteModuleInterface.vcxproj b/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteModuleInterface.vcxproj index dc666513d206..083aa868d3e2 100644 --- a/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteModuleInterface.vcxproj +++ b/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteModuleInterface.vcxproj @@ -1,6 +1,5 @@ - + @@ -41,6 +40,7 @@ + @@ -48,6 +48,7 @@ + Create diff --git a/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteModuleInterface.vcxproj.filters b/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteModuleInterface.vcxproj.filters index bb7e50932cb2..aa107e28db23 100644 --- a/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteModuleInterface.vcxproj.filters +++ b/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteModuleInterface.vcxproj.filters @@ -1,6 +1,5 @@  - + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} @@ -31,6 +30,9 @@ Header Files + + Header Files + @@ -42,6 +44,9 @@ Source Files + + Source Files + diff --git a/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteProcessManager.cpp b/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteProcessManager.cpp new file mode 100644 index 000000000000..b202f93f4e10 --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteProcessManager.cpp @@ -0,0 +1,267 @@ +#include "pch.h" +#include "AdvancedPasteProcessManager.h" + +#include +#include +#include +#include + +namespace +{ + std::optional 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(&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(&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(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(m_hProcess)); + }); +} + +void AdvancedPasteProcessManager::submit_task(std::function 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(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)); + } +} \ No newline at end of file diff --git a/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteProcessManager.h b/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteProcessManager.h new file mode 100644 index 000000000000..acef16bcee18 --- /dev/null +++ b/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteProcessManager.h @@ -0,0 +1,36 @@ +#pragma once +#include "pch.h" +#include +#include +#include +#include +#include +#include +#include + +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 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 m_enabled = false; // written on main thread, read on background thread + HANDLE m_hProcess = 0; + std::unique_ptr m_write_pipe; +}; \ No newline at end of file diff --git a/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp b/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp index 9fd120dca048..896b3627356e 100644 --- a/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp +++ b/src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp @@ -2,6 +2,7 @@ #include "pch.h" #include "AdvancedPasteConstants.h" +#include "AdvancedPasteProcessManager.h" #include #include "trace.h" #include "Generated Files/resource.h" @@ -16,8 +17,6 @@ #include #include -#include -#include #include BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, LPVOID /*lpReserved*/) @@ -66,6 +65,8 @@ namespace class AdvancedPaste : public PowertoyModuleIface { private: + + AdvancedPasteProcessManager m_process_manager; bool m_enabled = false; std::wstring app_name; @@ -73,13 +74,6 @@ class AdvancedPaste : public PowertoyModuleIface //contains the non localized key of the powertoy std::wstring app_key; - HANDLE m_hProcess; - - std::unique_ptr m_write_pipe; - - // Time to wait for process to close after sending WM_CLOSE signal - static const constexpr int MAX_WAIT_MILLISEC = 10000; - static const constexpr int NUM_DEFAULT_HOTKEYS = 4; Hotkey m_paste_as_plain_hotkey = { .win = true, .ctrl = true, .shift = false, .alt = true, .key = 'V' }; @@ -371,84 +365,6 @@ class AdvancedPaste : public PowertoyModuleIface } } - bool is_process_running() const - { - return WaitForSingleObject(m_hProcess, 0) == WAIT_TIMEOUT; - } - - void launch_process(const std::wstring& pipe_name) - { - Logger::trace(L"Starting AdvancedPaste process"); - 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 the Advanced Paste process"); - } - else - { - Logger::error(L"AdvancedPaste failed to start. {}", get_last_error_or_default(GetLastError())); - } - - TerminateProcess(m_hProcess, 1); - m_hProcess = sei.hProcess; - } - - std::optional get_pipe_name(const std::wstring& prefix) const - { - 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(&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(&uuid_chars)); - - return pipe_name; - } - - void launch_process_and_named_pipe() - { - const auto pipe_name = get_pipe_name(L"powertoys_advanced_paste_"); - - if (!pipe_name) - { - return; - } - - std::thread create_pipe_thread ([&]{ start_named_pipe_server(pipe_name.value()); }); - launch_process(pipe_name.value()); - create_pipe_thread.join(); - } - - void send_named_pipe_message(const std::wstring& message_type, const std::wstring& message_arg = L"") - { - 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)); - } - } - // Load the settings file. void init_settings() { @@ -691,66 +607,6 @@ class AdvancedPaste : public PowertoyModuleIface } } - void bring_process_to_front() - { - auto enum_windows = [](HWND hwnd, LPARAM param) -> BOOL { - HANDLE process_handle = reinterpret_cast(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, (LPARAM)m_hProcess); - } - - HRESULT start_named_pipe_server(const std::wstring& pipe_name) - { - 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 - 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) - { - return E_FAIL; - } - - // This call blocks until a client process connects to the pipe - BOOL connected = ConnectNamedPipe(hPipe, NULL); - if (!connected) - { - if (GetLastError() == ERROR_PIPE_CONNECTED) - { - return S_OK; - } - else - { - CloseHandle(hPipe); - } - return E_FAIL; - } - - m_write_pipe = std::make_unique(hPipe); - return S_OK; - } - public: AdvancedPaste() { @@ -854,28 +710,19 @@ class AdvancedPaste : public PowertoyModuleIface Logger::trace("AdvancedPaste::enable()"); Trace::AdvancedPaste_Enable(true); m_enabled = true; - - launch_process_and_named_pipe(); + m_process_manager.start(); }; void Disable(bool traceEvent) { if (m_enabled) { - send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_TERMINATE_APP_MESSAGE); - WaitForSingleObject(m_hProcess, 1500); - - m_write_pipe = nullptr; - - TerminateProcess(m_hProcess, 1); + m_process_manager.stop(); if (traceEvent) { Trace::AdvancedPaste_Enable(false); } - - CloseHandle(m_hProcess); - m_hProcess = 0; } m_enabled = false; @@ -892,11 +739,7 @@ class AdvancedPaste : public PowertoyModuleIface Logger::trace(L"AdvancedPaste hotkey pressed"); if (m_enabled) { - if (!is_process_running()) - { - Logger::trace(L"Launching new process"); - launch_process_and_named_pipe(); - } + m_process_manager.start(); // hotkeyId in same order as set by get_hotkeys if (hotkeyId == 0) @@ -917,22 +760,22 @@ class AdvancedPaste : public PowertoyModuleIface { // m_advanced_paste_ui_hotkey Logger::trace(L"Setting start up event"); - bring_process_to_front(); - send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_SHOW_UI_MESSAGE); + 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"); - send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_MARKDOWN_MESSAGE); + 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"); - send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_JSON_MESSAGE); + m_process_manager.send_message(CommonSharedConstants::ADVANCED_PASTE_JSON_MESSAGE); Trace::AdvancedPaste_Invoked(L"JsonDirect"); return true; } @@ -947,7 +790,7 @@ class AdvancedPaste : public PowertoyModuleIface Trace::AdvancedPaste_Invoked(std::format(L"{}Direct", kebab_to_pascal_case(id))); - send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_ADDITIONAL_ACTION_MESSAGE, id); + m_process_manager.send_message(CommonSharedConstants::ADVANCED_PASTE_ADDITIONAL_ACTION_MESSAGE, id); return true; } @@ -958,7 +801,7 @@ class AdvancedPaste : public PowertoyModuleIface Logger::trace(L"Starting custom action id={}", id); - send_named_pipe_message(CommonSharedConstants::ADVANCED_PASTE_CUSTOM_ACTION_MESSAGE, std::to_wstring(id)); + m_process_manager.send_message(CommonSharedConstants::ADVANCED_PASTE_CUSTOM_ACTION_MESSAGE, std::to_wstring(id)); Trace::AdvancedPaste_Invoked(L"CustomActionDirect"); return true; }