Skip to content

Feature:Support using Ctrl+C/X and Ctrl+V to invoke FastCopy - #64

Open
b1xcy wants to merge 9 commits into
HO-COOH:masterfrom
b1xcy:master
Open

Feature:Support using Ctrl+C/X and Ctrl+V to invoke FastCopy#64
b1xcy wants to merge 9 commits into
HO-COOH:masterfrom
b1xcy:master

Conversation

@b1xcy

@b1xcy b1xcy commented Aug 11, 2026

Copy link
Copy Markdown

feature:通过 Ctrl+C/X 和 Ctrl+V 调用 FastCopy

Ctrl+CCtrl+X 仍由资源管理器原生处理。这样可以保留标准 Shell IDataObject、文件系统路径,以及复制或移动操作标志。

新增一个随 MSIX 打包的后台辅助进程,用于接管粘贴操作:

  • 通过 MSIX 启动任务以及主程序启动流程运行。
  • 结合 RegisterHotKeyWH_KEYBOARD_LL 监听 Ctrl+V
  • 阻止资源管理器继续执行原生粘贴命令。
  • 对全局热键和键盘钩子产生的事件进行去重,避免重复启动任务。
  • 只接管资源管理器中的文件粘贴操作。
  • 通过活动 Shell View 获取目标目录。
  • 针对带标签页的新版资源管理器,使用 LocationURL 作为目录解析后备方案。
  • 优先通过 IShellItemArray 解析剪贴板文件和文件夹。
  • 在 Shell Item 解析失败时,通过标准 CF_HDROP 格式读取路径。
  • 通过 CFSTR_PREFERREDDROPEFFECT 区分复制和移动操作。
  • 将源文件路径写入 FastCopy 现有的任务记录文件格式。
  • 通过现有的 fastcopy:// 协议启动 FastCopy。
  • 移动操作成功交给 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 FastCopyKeyboardHook full-trust helper (MSIX startup task + launch from the main app) uses RegisterHotKey and a WH_KEYBOARD_LL hook 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 invoke fastcopy://. 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 shared Public helpers (KeyboardHookSettings, ModulePath, moved ShellWindows/WebBrowser2).

Reviewed by Cursor Bugbot for commit 247b2c1. Bugbot is set up for automated code reviews on this repo. Configure here.

@HO-COOH

HO-COOH commented Aug 13, 2026

Copy link
Copy Markdown
Owner

IShellWindow 不行!我之前已经踩过坑了,就是有用户装了那个tab工具,让多个explorer作为tab显示在一个窗口里,这时候我拿不到正确的那个,你可以翻一下issue

ResumeThread(hThread);
}

void RobocopyProcess::Suspend() const

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

怎么把我上一次commit的改动给还原了?

@b1xcy b1xcy Aug 13, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sry revert错commit了 我重新弄一下

@HO-COOH

HO-COOH commented Aug 13, 2026

Copy link
Copy Markdown
Owner
  1. 看了下,有大量重复代码,包括注册表那块以及打开程序那块,应该全部提出来放Public里。
  2. 能用wil管理资源的尽量用wil
  3. 有错误不要吞掉,直接抛出来,在最外面catch住,显示个MessageBox,不然会跟现在ShellExetension一样很难debug
  4. 对现有ShellExtension的改动单独开个PR

先感谢大佬帮忙

@b1xcy

b1xcy commented Aug 13, 2026

Copy link
Copy Markdown
Author

那我先把这个pr改成单hook,卸载的问题我再开一个
对于IShellWindow的坑,我看这个issue应该是拿错实例了,具体路径提取我走QueryActiveShellView → IFolderView → GetCurFolder,等我写完试试吧

@b1xcy b1xcy changed the title Fix:Bug of slow uninstallation & Feature:Support using Ctrl+C/X and Ctrl+V to invoke FastCopy Feature:Support using Ctrl+C/X and Ctrl+V to invoke FastCopy Aug 13, 2026
b1xcy added 2 commits August 13, 2026 18:59
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.
Comment thread Public/KeyboardHookSettings.h Outdated
{
DWORD value = 0;
DWORD valueSize = sizeof(value);
auto const result = RegGetValueW(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use wil::try_get_value_dword

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why copying from cotaskmem to a std::wstring? You can simply use its value later. Wrap it in wil::unique_cotaskmem_string

Comment thread FastCopy/KeyboardHookController.cpp Outdated
&startupInfo,
&processInfo))
{
CloseHandle(processInfo.hThread);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use wil::unique_process_information

Comment thread FastCopy/KeyboardHookController.cpp Outdated
return;
}

std::wstring modulePath(32768, L'\0');

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary. Use std::array<wchar_t, MAX_PATH> is enough. Actually already have one in currentDllPath(). Move that into public and reuse it.

Comment thread FastCopy/KeyboardHookController.cpp Outdated
auto const hookPath = std::filesystem::path{ modulePath }.parent_path() / L"FastCopyKeyboardHook.exe";
if (!std::filesystem::exists(hookPath))
{
return;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't silence this error.

Comment thread FastCopyKeyboardHook/main.cpp Outdated
return 0;
}

auto const singleton = CreateMutexW(nullptr, FALSE, KeyboardHookSettings::SingletonName);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use wil::unique_mutex

Comment thread FastCopyKeyboardHook/main.cpp Outdated
return 0;
}

auto const comResult = OleInitialize(nullptr);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use wil::scope_exit to uninitialize ole

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please refactor out this file. Definitely put the message window into a class. Private constant goes into class member.

Comment thread FastCopyKeyboardHook/ExplorerWindow.cpp Outdated
{
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{};

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just return the array?

b1xcy added 4 commits August 14, 2026 00:44
…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.
Comment thread FastCopyKeyboardHook/main.cpp Outdated
}
}
auto const isPaste = event->vkCode == L'V';
if (isReplayInput || !isPaste)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refactor this if chain

Comment thread FastCopyKeyboardHook/main.cpp Outdated
}
}

class KeyboardHookApp

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread FastCopyKeyboardHook/main.cpp Outdated
static KeyboardHookApp* s_instance;

HWND m_messageWindow{};
HHOOK m_keyboardHook{};

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use wil::unique_hhook

Comment thread FastCopyKeyboardHook/ExplorerWindow.cpp Outdated
auto const className = GetWindowClass(expectedForegroundWindow);
if (std::wcscmp(className.data(), L"Progman") == 0 || std::wcscmp(className.data(), L"WorkerW") == 0)
{
PWSTR desktopPath{};

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use wil::unique_cotaskmem_string

Comment thread FastCopyKeyboardHook/ExplorerWindow.cpp Outdated
// 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

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move into its separate .h and .cpp file

Comment thread FastCopyKeyboardHook/ExplorerWindow.cpp Outdated
public:
ExplorerFolderResolver()
{
THROW_IF_FAILED(CoCreateInstance(CLSID_ShellWindows, nullptr, CLSCTX_ALL, IID_PPV_ARGS(&m_shellWindows)));

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already have a ShellWindows class, a IShellWindow wrapper in FastCopyShellExtension. Please use that.

b1xcy and others added 2 commits August 14, 2026 21:08
…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

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 4 potential issues.

Fix All in Cursor

❌ 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);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 247b2c1. Configure here.

}
}

return locationFallback;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 247b2c1. Configure here.

{
ShowError(e.what());
}
return 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 247b2c1. Configure here.

if (!singleton || GetLastError() == ERROR_ALREADY_EXISTS)
{
return 0;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 247b2c1. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants