-
Notifications
You must be signed in to change notification settings - Fork 6.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Advanced Paste] Use background thread for runner-Advanced Paste inte…
…raction
- Loading branch information
1 parent
5ef9187
commit b1f3681
Showing
5 changed files
with
325 additions
and
173 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
267 changes: 267 additions & 0 deletions
267
src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteProcessManager.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,267 @@ | ||
#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)); | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
src/modules/AdvancedPaste/AdvancedPasteModuleInterface/AdvancedPasteProcessManager.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
#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; // writen on main thread, read on background thread | ||
HANDLE m_hProcess = 0; | ||
std::unique_ptr<CAtlFile> m_write_pipe; | ||
}; |
Oops, something went wrong.
b1f3681
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@check-spelling-bot Report
🔴 Please review
See the 📜action log or 📝 job summary for details.
Unrecognized words (1)
writen
These words are not needed and should be removed
accctrl aclapi appdata Appium appmodel atlbase atlcom atlfile atlstr bootstrapper caniuse ceq cguid Cmds cne codicon comdef commandline commctrl commdlg comutil consts contentdialog cppwinrt CRSEL crx dcommon dcomp DCs desktopwindowxamlsource devpkey dxgidebug dxgiformat emmintrin Emoji endpointvolume evntrace exdisp Functiondiscoverykeys guiddef hinstance hstring Intelli ipreviewhandlervisualssetfont junja Knownfolders lmcons LONGLONG lpt LTRB mfapi mfidl mfobjects mftransform Minimatch mmdeviceapi mmsystem msedge msiquery newdev nodoc notlike ntfs Objbase objidl outputtype pathcch Pnp Preinstalled processthreadsapi propkey propvarutil redistributable Renamer reparse restrictederrorinfo roadmap ruleset runtimes shellapi shellscalingapi shldisp shlobj stl strsafe strutil subquery SWC tailwindcss tapp thumbcache tlhelp Toolset touchpad Tsd uninstantiated uniquifier Unknwn unregistering urlmon USERDATA Uxtheme verrsrc wcautil wincodec Wincodecsdk windef windowsapp windowsx winerror winevt winexe winforms winsdkver winternl wsl wtsapiTo accept these unrecognized words as correct and remove the previously acknowledged and now absent words, you could run the following commands
... in a clone of the [email protected]:microsoft/PowerToys.git repository
on the
dev/ani/advanced-paste-async-process-management
branch (ℹ️ how do I use this?):If the flagged items are 🤯 false positives
If items relate to a ...
binary file (or some other file you wouldn't want to check at all).
Please add a file path to the
excludes.txt
file matching the containing file.File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.
^
refers to the file's path from the root of the repository, so^README\.md$
would exclude README.md (on whichever branch you're using).well-formed pattern.
If you can write a pattern that would match it,
try adding it to the
patterns.txt
file.Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.
Note that patterns can't match multiline strings.