Windows注册鼠标钩子,获取用户选中的文本

发布于:2025-04-12 ⋅ 阅读:(36) ⋅ 点赞:(0)

注册鼠标钩子

// 注册鼠标钩子
HHOOK hMouseHook;
hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, 
    MouseProc, 
    GetModuleHandle(NULL), 
    0);

// 取消鼠标钩子
UnhookWindowsHookEx(hMouseHook);
hMouseHook = nullptr;

上述代码中MouseProc方法用于处理系统的鼠标消息

处理鼠标消息

LRESULT MouseHook::MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    static QPoint pos;
    static qint64 lastTriggerTime = 0;
    if (nCode >= 0) {
        if (wParam == WM_LBUTTONDOWN) {
            pos = QCursor::pos();           
        }
        else if (wParam == WM_LBUTTONUP) {
            if (pos != QCursor::pos()) {
                timer->start(280);  //拖拽
            }
            qint64 currentTime = QDateTime::currentMSecsSinceEpoch();
            if (currentTime - lastTriggerTime <= 500) {
                timer->start(280); //双击
            }
            lastTriggerTime = currentTime;
        }
    }
    return CallNextHookEx(hMouseHook, nCode, wParam, lParam);
}

这段代码中,我们使用一个QTimer来处理双击或拖拽选中文本的操作。

文本选中后的处理方法

void MouseHook::processMouseUp()
{
    auto textGetter = new TextGetter(this);
    connect(textGetter, 
        &TextGetter::resultReady, 
        this, 
        &MouseHook::onTextReady, 
        Qt::ConnectionType::QueuedConnection);
    textGetter->start();
}

TextGetter是一个继承自QThread的类,我们将在一个新线程中去获取用户选中的文本。

获取选中文本的第一种情况

QString TextGetter::getSelectedTextByUIAutomation() {
    try {
        auto hr = CoInitialize(NULL);
        if (FAILED(hr))
        {
            CoUninitialize();
            return "";
        }
        CComPtr<IUIAutomation> automation;
        hr = CoCreateInstance(CLSID_CUIAutomation, nullptr, CLSCTX_INPROC_SERVER, IID_IUIAutomation,(void**)(&automation));
        if (FAILED(hr))
        {
            CoUninitialize();
            return "";
        }
        CComPtr<IUIAutomationElement> focusedElement;
        hr = automation->GetFocusedElement(&focusedElement);
        if (FAILED(hr) || !focusedElement)
        {
            CoUninitialize();
            return "";
        }
        CComPtr<IUIAutomationTextPattern> textPattern;
        hr = focusedElement->GetCurrentPatternAs(UIA_TextPatternId, IID_PPV_ARGS(&textPattern));
        if (FAILED(hr) || !textPattern)
        {
            CoUninitialize();
            return "";
        }
        CComPtr<IUIAutomationTextRangeArray> selection;
        hr = textPattern->GetSelection(&selection);
        if (FAILED(hr) || !selection)
        {
            CoUninitialize();
            return "";
        }
        CComPtr<IUIAutomationTextRange> range;
        hr = selection->GetElement(0, &range);
        if (FAILED(hr) || !range)
        { 
            CoUninitialize();
            return "";
        }
        CComBSTR text;
        range->GetText(-1, &text);
        std::wstring ws(text, SysStringLen(text));
        CoUninitialize();
        return QString::fromStdWString(ws);
    }
    catch (std::exception& e) {
        return "";
    }       
}

这段代码使用 Microsoft UI Automation (UIA) API 从当前具有焦点的 UI 元素中获取选中文本的方法。但有的时候这种方法获取不到想要的文本(老式窗口中的文本)

获取选中文本的第二种情况

当第一种情况获取到的文本是空时,就要尝试第二种情况

auto hwnd = getCurrentHwnd();
if (!hwnd) {
    return "";
}
auto cache = cacheClipboard();
sendCtrlC();
str = getClipboardText();
if (str.isEmpty()) {
    CloseClipboard();
    return "";
}
restoreClipboard(cache);
CloseClipboard();

这种情况,先获取系统当前聚焦的窗口,然后缓存当前剪切板,然后发送Ctrl+C复制此窗口选中的文本,然后获取剪切板内的文本,然后把之前缓存的内容存入剪切板。

下面我们看看这些实现代码:

获取系统当前聚焦的窗口

HWND TextGetter::getCurrentHwnd()
{
    HWND hwnd = GetForegroundWindow();
    DWORD threadId = GetWindowThreadProcessId(hwnd, NULL);
    AttachThreadInput(GetCurrentThreadId(), threadId, TRUE);
    hwnd = GetFocus();
    AttachThreadInput(GetCurrentThreadId(), threadId, FALSE);
    POINT pt;
    GetCursorPos(&pt);
    RECT rect;
    GetWindowRect(hwnd, &rect);
    if (pt.x<rect.left || pt.y<rect.top || pt.x>rect.right || pt.y>rect.bottom) {
        return nullptr;
    }
    return hwnd;
}

如果聚焦的窗口与鼠标所在位置的窗口不是一个窗口,那么我们取消任务。

缓存剪切板的内容

ClipboardData TextGetter::cacheClipboard()
{
    OpenClipboard(nullptr);
    ClipboardData cache;
    UINT format = 0;
    while ((format = EnumClipboardFormats(format)) != 0) {
        HANDLE hData = GetClipboardData(format);
        if (hData) {
            SIZE_T size = GlobalSize(hData);
            HGLOBAL hCopy = GlobalAlloc(GMEM_MOVEABLE, size);
            if (hCopy) {
                void* pDest = GlobalLock(hCopy);
                void* pSource = GlobalLock(hData);
                if (pDest && pSource) {
                    memcpy(pDest, pSource, size);
                }
                GlobalUnlock(hData);
                GlobalUnlock(hCopy);
                cache.push_back({ format, hCopy });
            }
        }
    }
    EmptyClipboard();
    CloseClipboard();
    return cache;
}

发送Ctrl+C按键消息

void TextGetter::sendCtrlC()
{
    INPUT inputs[4] = { 0 };
    inputs[0].type = INPUT_KEYBOARD;
    inputs[0].ki.wVk = VK_CONTROL;
    inputs[1].type = INPUT_KEYBOARD;
    inputs[1].ki.wVk = 'C';
    inputs[2].type = INPUT_KEYBOARD;
    inputs[2].ki.wVk = 'C';
    inputs[2].ki.dwFlags = KEYEVENTF_KEYUP;
    inputs[3].type = INPUT_KEYBOARD;
    inputs[3].ki.wVk = VK_CONTROL;
    inputs[3].ki.dwFlags = KEYEVENTF_KEYUP;
    SendInput(ARRAYSIZE(inputs), inputs, sizeof(INPUT));
    QThread::msleep(360);
}

按键发送成功后需要等待360毫秒

获取剪切板的内容

QString TextGetter::getClipboardText()
{
    OpenClipboard(nullptr);
    if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) {
        CloseClipboard();
        return "";
    }
    HANDLE hData = GetClipboardData(CF_UNICODETEXT);
    if (hData == nullptr) {
        CloseClipboard();
        return "";
    }
    LPCWSTR pText = static_cast<LPCWSTR>(GlobalLock(hData));
    if (pText == nullptr) {
        CloseClipboard();
        return "";
    }
    QString result = QString::fromWCharArray(pText);
    GlobalUnlock(hData);
    CloseClipboard();
    return result;
}

不要怀疑发送按键Ctrl+C这个方案是否可行,有道词典就是这么干的。


网站公告

今日签到

点亮在社区的每一天
去签到