
像豆包电脑那样,鼠标在桌面上拖动一个文件,刚开始拖动,还没进入任何窗口,豆包就能感知到,然后在屏幕右下角弹出提示,可以帮忙解读文件。这个是怎么实现的? 目前,通过鼠标钩子判断是否在拖拽,如果在拖拽,就通过 OleGetClipboard 函数从剪贴板获取文件信息,但是剪贴板里面没有文件相关的信息。代码如下:
#include <windows.h> #include <shlobj.h> #include <vector> #include <string> #include <iostream> #include <chrono> #include <thread> HHOOK g_mouseHook; bool g_isDragging = false; POINT g_dragStartPos = { 0, 0 }; const int DRAG_THRESHOLD = 3; // 拖动阈值(像素) void ExtractFileInfoFromDropClipboard(); void CheckOtherDataFormats(IDataObject* pDataObject); void ExtractFileTypeInfo(const std::wstring& filePath); LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode == HC_ACTION) { MSLLHOOKSTRUCT* pMouse = (MSLLHOOKSTRUCT*)lParam; if (wParam == WM_LBUTTONDOWN) { //std::cout << "Mouse Button Down at (" << pMouse->pt.x << ", " << pMouse->pt.y << ")\n"; // 你可以在这里检测是否是拖拽开始的条件 // 记录拖动起始位置 g_dragStartPos = pMouse->pt; g_isDragging = false; } if (wParam == WM_MOUSEMOVE) { // 检测鼠标是否有拖拽操作 if (GetAsyncKeyState(VK_LBUTTON) & 0x8000) { // 检查是否超过拖动阈值 int deltaX = abs(pMouse->pt.x - g_dragStartPos.x); int deltaY = abs(pMouse->pt.y - g_dragStartPos.y); if (!g_isDragging && (deltaX > DRAG_THRESHOLD || deltaY > DRAG_THRESHOLD)) { g_isDragging = true; std::cout << "move begin..." << std::endl; // 尝试从拖放剪贴板获取文件信息 ExtractFileInfoFromDropClipboard(); } } } if (wParam == WM_LBUTTONUP) { if (g_isDragging) { std::cout << "move end" << std::endl; g_isDragging = false; } } } return CallNextHookEx(g_mouseHook, nCode, wParam, lParam); } // 从拖放剪贴板提取文件信息 void ExtractFileInfoFromDropClipboard() { HRESULT hr = OleInitialize(nullptr); if (FAILED(hr)) { std::cerr << "OleInitialize failed: " << hr << std::endl; return; } IDataObject* pDataObject = nullptr; // 获取拖放剪贴板数据 hr = OleGetClipboard(&pDataObject); if (SUCCEEDED(hr) && pDataObject) { FORMATETC fmtetc = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; if (SUCCEEDED(pDataObject->QueryGetData(&fmtetc))) { STGMEDIUM stgmed; // 查询 HDROP 数据 hr = pDataObject->GetData(&fmtetc, &stgmed); if (SUCCEEDED(hr)) { HDROP hDrop = static_cast<HDROP>(GlobalLock(stgmed.hGlobal)); if (hDrop) { // 获取文件数量 UINT fileCount = DragQueryFile(hDrop, 0xFFFFFFFF, nullptr, 0); std::cout << "drag file count: " << fileCount << std::endl; // 遍历所有文件 for (UINT i = 0; i < fileCount; i++) { // 获取文件路径长度 UINT pathLength = DragQueryFile(hDrop, i, nullptr, 0); if (pathLength > 0) { std::vector<TCHAR> buffer(pathLength + 1); DragQueryFile(hDrop, i, buffer.data(), pathLength + 1); std::wstring filePath(buffer.data()); std::wcout << L"file " << (i + 1) << L": " << filePath << std::endl; // 获取文件属性信息 ExtractFileTypeInfo(filePath); } } GlobalUnlock(stgmed.hGlobal); } ReleaseStgMedium(&stgmed); } } else { std::wcout << L"CF_HDROP format not supported" << std::endl; // 尝试其他数据格式 //CheckOtherDataFormats(pDataObject); } pDataObject->Release(); } else { std::wcerr << L"can not get clipboard data" << std::endl; } OleUninitialize(); } // 检查其他数据格式 void CheckOtherDataFormats(IDataObject* pDataObject) { // 查询支持的数据格式 IEnumFORMATETC* pEnumFormat = nullptr; HRESULT hr = pDataObject->EnumFormatEtc(DATADIR_GET, &pEnumFormat); if (SUCCEEDED(hr) && pEnumFormat) { FORMATETC fmtetc; ULONG fetched = 0; std::cout << "data format in clipboard:" << std::endl; while (pEnumFormat->Next(1, &fmtetc, &fetched) == S_OK && fetched == 1) { TCHAR formatName[256]; if (GetClipboardFormatName(fmtetc.cfFormat, formatName, 256) > 0) { std::wcout << L"format1: " << formatName << L" (ID: " << fmtetc.cfFormat << L")" << std::endl; } else { // 标准格式 std::string stdFormatName; switch (fmtetc.cfFormat) { case CF_TEXT: stdFormatName = "CF_TEXT"; break; case CF_UNICODETEXT: stdFormatName = "CF_UNICODETEXT"; break; case CF_BITMAP: stdFormatName = "CF_BITMAP"; break; case CF_DIB: stdFormatName = "CF_DIB"; break; case CF_HDROP: stdFormatName = "CF_HDROP"; break; case CF_LOCALE: stdFormatName = "CF_LOCALE"; break; case CF_OEMTEXT: stdFormatName = "CF_OEMTEXT"; break; default: stdFormatName = "unknown"; break; } std::cout << "format2: " << stdFormatName << " (ID: " << fmtetc.cfFormat << ")" << std::endl; } } pEnumFormat->Release(); } } // 提取文件类型信息 void ExtractFileTypeInfo(const std::wstring& filePath) { // 获取文件属性 DWORD attr = GetFileAttributes(filePath.c_str()); if (attr != INVALID_FILE_ATTRIBUTES) { if (attr & FILE_ATTRIBUTE_DIRECTORY) { std::wcout << L" type: directory" << std::endl; } else { std::wcout << L" type: file" << std::endl; // 获取文件扩展名 size_t dotPos = filePath.find_last_of(L'.'); if (dotPos != std::wstring::npos) { std::wstring extension = filePath.substr(dotPos); std::wcout << L" extension: " << extension << std::endl; } // 获取文件大小 HANDLE hFile = CreateFile(filePath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (hFile != INVALID_HANDLE_VALUE) { LARGE_INTEGER fileSize; if (GetFileSizeEx(hFile, &fileSize)) { std::wcout << L" size: " << fileSize.QuadPart << L" bytes" << std::endl; } CloseHandle(hFile); } } // 显示文件属性 std::wcout << L" properties: "; if (attr & FILE_ATTRIBUTE_READONLY) std::wcout << L"[read-only]"; if (attr & FILE_ATTRIBUTE_HIDDEN) std::wcout << L"[hidden]"; if (attr & FILE_ATTRIBUTE_SYSTEM) std::wcout << L"[system]"; if (attr & FILE_ATTRIBUTE_ARCHIVE) std::wcout << L"[archive]"; std::wcout << std::endl; } } void InstallMouseHook() { g_mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseProc, NULL, 0); if (g_mouseHook == NULL) { std::cerr << "Failed to install mouse hook!" << std::endl; } } int main() { InstallMouseHook(); // 进入消息循环 MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } // 卸载钩子 UnhookWindowsHookEx(g_mouseHook); return 0; } 1 BBrother 16 天前 创建一个全屏的透明窗口试试? |
2 huihushijie1996 16 天前 |
3 huihushijie1996 16 天前 @huihushijie1996 只需要截获桌面窗口的消息即可 |
4 skylord OP 在 windows 资源管理器里拖动也能截获吗? |
6 zjsxwc 16 天前 问下 ai ,就有代码:用 python 实现:捕获系统级的鼠标操作与文件拖拽 若要获取非自身窗口的鼠标点击或文件拖拽(如系统全局范围),不能通过 DWM 实现,需借助鼠标钩子(如 SetWindowsHookEx 函数设置 WH_MOUSE_LL 钩子)捕获全局鼠标事件。而获取全局文件拖拽时,可结合 Shell 扩展编程或钩子监听资源管理器的文件选择操作,通过获取 SysListView32 控件(资源管理器文件列表控件)的句柄,进一步查询拖拽文件的相关信息,但这种方式需注意权限和系统兼容性问题。 |
7 worldhandsomeboy 16 天前 鼠标相关的 api 有一些属于焦点的函数,在你左击文件拖拽的时候,焦点所在的文件就是一个对象,包括路径、类型这些。 |
9 sir283 16 天前 你要用管理员权限运行你的软件啊,豆包都是用管理员权限 hook 的。 |
11 guanzhangzhang 16 天前 |
12 skylord OP @guanzhangzhang 我们公司开发了个智能助手,也要这个功能 |
13 momo1999 16 天前 拖拽应该是个单独的结构,不影响剪贴板吧,要不然我拖个文件,我之前复制的东西就没了? |
16 ljpCN 16 天前 |
17 guanzhangzhang 16 天前 @skylord #12 看看有啥类似 processmonitor 啥的看看能不能监听到 qq 的这块大概 dll ,看看符号表啥的 |
18 nnnnnnamgn 16 天前 豆包那个实现应该很复杂,不如搞一个类似 360 加速器那样的小的悬浮窗,让用户自己拖拽文件进去 |
22 skylord |
23 worldhandsomeboy 15 天前 创建了个 demo 监听粘贴板,在拖动桌面文件过程中很多时候没有写入剪贴板,有时候写入了但复现不出来。 下载了 window 的豆包,没看到有这个功能。 |
24 dode 15 天前 可能是监控鼠标位置 |
26 skylord OP @worldhandsomeboy 功能是有的,你在拖动 doc ,txt 等文档类的文件时,右下角会弹出一个 tips ,告诉你它可以帮你解读文件 |
27 nilaoda 15 天前 问了 Gemini 3 Pro Preview ,可以使用 UIAutomationClient 监听 SysDragImage 元素来实现。 整个流程如下: 1. 利用 UI Automation 监听 SysDragImage 出现以捕获拖拽行为 2. 结合鼠标坐标定位与 Shell COM 接口预先读取并校验选中文件的类型 3. 只有当文件类型符合要求时才弹出悬浮窗,且窗口的关闭逻辑完全依赖鼠标按键状态监测,实现精准触发 .NET 调用比较方便,让他写了一个 WPF 的 demo ,不知道你要的是不是这种效果? ![]() https://gist.github.com/nilaoda/26b5f32bb383a3285cbe89434c30a232 让 AI 用 C/C++实现一下就好。 BTW ,可以研究研究 win11 顶部分享框的实现…… |
28 ysc3839 15 天前 via Android 不知道 SetWinEventHook 的 EVENT_OBJECT_DRAGSTART 等事件是否可行? https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwineventhook |