Feature:Support using Ctrl+C/X and Ctrl+V to invoke FastCopy - #64
Conversation
|
|
| ResumeThread(hThread); | ||
| } | ||
|
|
||
| void RobocopyProcess::Suspend() const |
先感谢大佬帮忙 |
|
那我先把这个pr改成单hook,卸载的问题我再开一个 |
Keep Ctrl+C and Ctrl+X in Explorer so the Shell produces its standard IDataObject, including the preferred copy or move drop effect. Start a packaged background helper at sign-in and app launch. It combines RegisterHotKey with WH_KEYBOARD_LL to observe Ctrl+V, suppress Explorer's native accelerator, and deduplicate the two input paths. Resolve the foreground Explorer folder through IShellWindows, using the active ShellView first and LocationURL as a tab-compatible fallback. Read filesystem items through IShellItemArray with CF_HDROP fallback, serialize them in FastCopy's existing task-file format, and launch the fastcopy protocol. Clear the clipboard after a move; on any failure, temporarily release the hotkey and replay native Ctrl+V. Add a localized settings toggle backed by HKCU and package the helper as an enabled startup task. Verified: user-tested file and folder copy/move with Ctrl+C/Ctrl+X then Ctrl+V using the signed 1.2.1.8 MSIX; FastCopyKeyboardHook Package|x64 builds with 0 warnings and 0 errors; git diff --cached --check passes.
The desktop window (Progman/WorkerW) is not an Explorer frame, so the keyboard hook let Ctrl+V fall through to the native paste. Treat the desktop as a valid target and resolve its folder through FOLDERID_Desktop.
| { | ||
| DWORD value = 0; | ||
| DWORD valueSize = sizeof(value); | ||
| auto const result = RegGetValueW( |
There was a problem hiding this comment.
Please wrap all the COM types you used into a class. Throw if any of them failed, then catch them outside
|
|
||
| struct ClipboardFileTransfer | ||
| { | ||
| std::vector<std::wstring> paths; |
There was a problem hiding this comment.
Why copying from cotaskmem to a std::wstring? You can simply use its value later. Wrap it in wil::unique_cotaskmem_string
| &startupInfo, | ||
| &processInfo)) | ||
| { | ||
| CloseHandle(processInfo.hThread); |
There was a problem hiding this comment.
Use wil::unique_process_information
| return; | ||
| } | ||
|
|
||
| std::wstring modulePath(32768, L'\0'); |
There was a problem hiding this comment.
Not necessary. Use std::array<wchar_t, MAX_PATH> is enough. Actually already have one in currentDllPath(). Move that into public and reuse it.
| auto const hookPath = std::filesystem::path{ modulePath }.parent_path() / L"FastCopyKeyboardHook.exe"; | ||
| if (!std::filesystem::exists(hookPath)) | ||
| { | ||
| return; |
| return 0; | ||
| } | ||
|
|
||
| auto const singleton = CreateMutexW(nullptr, FALSE, KeyboardHookSettings::SingletonName); |
| return 0; | ||
| } | ||
|
|
||
| auto const comResult = OleInitialize(nullptr); |
There was a problem hiding this comment.
Use wil::scope_exit to uninitialize ole
There was a problem hiding this comment.
Please refactor out this file. Definitely put the message window into a class. Private constant goes into class member.
| { | ||
| std::array<wchar_t, 256> buffer{}; | ||
| auto const length = GetClassNameW(window, buffer.data(), static_cast<int>(buffer.size())); | ||
| return length > 0 ? std::wstring{ buffer.data(), static_cast<size_t>(length) } : std::wstring{}; |
…ard hook - KeyboardHookSettings: use wil::reg::try_get_value_dword / create_unique_key - ClipboardFileTransfer: hold GetDisplayName results in wil::unique_cotaskmem_string - ExplorerWindow: wrap the COM types in an ExplorerFolderResolver class and throw (wil::ResultException) on COM failures; GetWindowClass returns the array instead of copying to a std::wstring - main: refactor into a KeyboardHookApp class (message window and private constants become class members), use wil::unique_mutex for the singleton and wil::scope_exit for OleUninitialize; failures are caught at the message loop and wWinMain and shown in a message box - add the WIL NuGet dependency to FastCopyKeyboardHook
…p errors - Move the module path helper out of DllIconFormatter into Public/ModulePath and reuse it in KeyboardHookController instead of the 32K scratch buffer - KeyboardHookController: use wil::unique_process_information and throw when the hook executable is missing instead of silently returning - SettingsViewModel: catch hook startup errors and show them
ClipboardFileTransfer::paths now holds wil::unique_cotaskmem_string instead of copying every display name into a std::wstring; the values are consumed (normalized and serialized) only when the record file is written.
| } | ||
| } | ||
| auto const isPaste = event->vkCode == L'V'; | ||
| if (isReplayInput || !isPaste) |
| } | ||
| } | ||
|
|
||
| class KeyboardHookApp |
There was a problem hiding this comment.
Please separate this class into 4 classes, a Window class, a keyboard hook class, a HotKey class (for that paste hot key using RAII to unregister automatically) and an App class. Also, please make private class member start s with lower case letter
| static KeyboardHookApp* s_instance; | ||
|
|
||
| HWND m_messageWindow{}; | ||
| HHOOK m_keyboardHook{}; |
| auto const className = GetWindowClass(expectedForegroundWindow); | ||
| if (std::wcscmp(className.data(), L"Progman") == 0 || std::wcscmp(className.data(), L"WorkerW") == 0) | ||
| { | ||
| PWSTR desktopPath{}; |
There was a problem hiding this comment.
Use wil::unique_cotaskmem_string
| // boundary; failures that are expected for individual entries are | ||
| // skipped, and a window whose active view does not support the older | ||
| // IFolderView interfaces falls back to the browser location URL. | ||
| class ExplorerFolderResolver |
There was a problem hiding this comment.
Move into its separate .h and .cpp file
| public: | ||
| ExplorerFolderResolver() | ||
| { | ||
| THROW_IF_FAILED(CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&m_shellWindows))); |
There was a problem hiding this comment.
I already have a ShellWindows class, a IShellWindow wrapper in FastCopyShellExtension. Please use that.
…lasses - Move the ShellWindows/WebBrowser2 wrappers from FastCopyShellExtension into Public/ (drop the ATL dependency) and reuse them from the keyboard hook's ExplorerFolderResolver instead of a private implementation - ExplorerFolderResolver moves into its own .h/.cpp; ExplorerWindow keeps only the window checks, and the desktop branch now uses wil::unique_cotaskmem_string - Split the hook app into four classes: PasteWindow (message window), PasteHotKey (RAII register/unregister), KeyboardHook (RAII hook via wil::unique_hhook) and KeyboardHookApp (composition and paste logic); private members start with a lower-case letter, and the keyboard event handler is restructured to early-return instead of nested ifs
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 4 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 247b2c1. Configure here.
| { | ||
| PostMessageW(window, WM_CLOSE, 0, 0); | ||
| } | ||
| } |
There was a problem hiding this comment.
Stop cannot find hook window
High Severity
Stop uses FindWindowW to locate the hook HWND, but PasteWindow is created as an HWND_MESSAGE window. FindWindowW never searches message-only windows, so disable always fails to post WM_CLOSE. The registry flag flips off while the helper keeps intercepting Ctrl+V until reboot. The existing SettingsChangeListener already uses FindWindowEx(HWND_MESSAGE, ...) for this case.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 247b2c1. Configure here.
| } | ||
| } | ||
|
|
||
| return locationFallback; |
There was a problem hiding this comment.
Wrong tab folder fallback
High Severity
locationFallback keeps the first ShellWindows entry that shares the frame HWND, and focus is only tested against the shell view window, not the shell browser. With Win11 tabs, focus in the navigation pane or a failed IFolderView path can return another tab's LocationURL, so FastCopy pastes into the wrong directory.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 247b2c1. Configure here.
| { | ||
| ShowError(e.what()); | ||
| } | ||
| return 0; |
There was a problem hiding this comment.
Paste not restored on errors
High Severity
The low-level hook already eats Ctrl+V before HandlePaste runs. Soft failures call ReplayPaste, but COM/THROW_IF_FAILED errors are caught in OnWindowMessage, show a message box, and never replay. Folder resolution helpers throw on routine COM failures, so Explorer paste can disappear entirely instead of falling back to the native paste.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 247b2c1. Configure here.
| if (!singleton || GetLastError() == ERROR_ALREADY_EXISTS) | ||
| { | ||
| return 0; | ||
| } |
There was a problem hiding this comment.
Hook before singleton check
Medium Severity
KeyboardHook is installed during KeyboardHookApp construction, while the singleton mutex is only taken later in Run. Every FastCopy launch calls Start and can spawn a short-lived second process that briefly installs another WH_KEYBOARD_LL hook before exiting, creating a race where two helpers can both handle the same paste.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 247b2c1. Configure here.


feature:通过 Ctrl+C/X 和 Ctrl+V 调用 FastCopy
Ctrl+C和Ctrl+X仍由资源管理器原生处理。这样可以保留标准 ShellIDataObject、文件系统路径,以及复制或移动操作标志。新增一个随 MSIX 打包的后台辅助进程,用于接管粘贴操作:
RegisterHotKey和WH_KEYBOARD_LL监听Ctrl+V。LocationURL作为目录解析后备方案。IShellItemArray解析剪贴板文件和文件夹。CF_HDROP格式读取路径。CFSTR_PREFERREDDROPEFFECT区分复制和移动操作。fastcopy://协议启动 FastCopy。Ctrl+V。同时新增了本地化设置开关,允许用户启用或关闭资源管理器快捷键集成功能。
Note
Medium Risk
Global low-level keyboard hooking and clipboard interception affect core Explorer paste behavior; mistakes could block paste or mis-route copy/move, though fallback replay and a user toggle limit blast radius.
Overview
Adds Explorer keyboard integration so Ctrl+V in File Explorer can route file paste through RoboCopyEx instead of the shell copy engine, while Ctrl+C/X stay on Explorer’s native clipboard behavior.
A new
FastCopyKeyboardHookfull-trust helper (MSIX startup task + launch from the main app) usesRegisterHotKeyand aWH_KEYBOARD_LLhook to catch Ctrl+V, suppress Explorer’s paste when appropriate, resolve the destination folder (shell view / tabbed Explorer fallbacks), read file paths from the clipboard, write the existing record-file format, and invokefastcopy://. Failed handoffs replay native Ctrl+V; successful move pastes clear the clipboard.The main app gains
KeyboardHookController(start/stop the helper, registry-backed enablement), a settings toggle with localized strings, and sharedPublichelpers (KeyboardHookSettings,ModulePath, movedShellWindows/WebBrowser2).Reviewed by Cursor Bugbot for commit 247b2c1. Bugbot is set up for automated code reviews on this repo. Configure here.