使用Windows Hooks拦截键盘事件的问题

问题描述 投票:0回答:1

我正在致力于使用 WinAPI 创建一个类似于 Windows 版 Vim 的命令行界面 (CLI) 编辑器。目前,我专注于实现编辑模式(通过按“i”触发)和命令模式(通过按 Escape 激活)的逻辑,因此我知道解决此问题的两种方法:

1.) 第一种方法是创建一个 while 循环,连续侦听 Escape 键。该循环将在单独的线程上无限期地运行,以避免干扰程序流程。但这种方法的主要问题是这种方法可能会导致 CPU 消耗过高,减慢程序速度并可能导致与 CPU 相关的问题。

2.) 第二种方法涉及使用 Windows 挂钩。我编写了一个简单的程序来拦截键盘事件并检测“i”和 Escape 键。我已经实现了一个单独的变量来跟踪按键的来源。但是,我遇到了编辑器的问题,因为它似乎无法正常工作,并且无论我们在执行过程中按哪个键,光标都保持在之前的位置。有人可以帮我吗? 谢谢你。

这是相同的代码:-


HANDLE hConsole;
bool editMode = true;

void moveCursorToLastRow();
void moveCursorToFirstRow();

LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode >= 0) {
        KBDLLHOOKSTRUCT* kbStruct = (KBDLLHOOKSTRUCT*)lParam;
        DWORD vkCode = kbStruct->vkCode;
        if (vkCode == VK_ESCAPE && editMode == true) {
            editMode = false;
            moveCursorToLastRow();
        }
        else if (vkCode == 'I' && editMode == false) {
            editMode = true;
            moveCursorToFirstRow();
        }
    }
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

void moveCursorToFirstRow() {
    // Set the cursor position to the first row
    COORD cursorPosition = { 3, 2 };
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), cursorPosition);
}

void moveCursorToLastRow() {
    // Get the console screen buffer size
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);

    // Set the cursor position to the last row
    COORD cursorPosition = { 3, csbi.dwSize.Y - 1 };
    SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), cursorPosition);
}

int main(int argc, char* argv[]) {

    HINSTANCE hInstance = GetModuleHandle(NULL);

    HHOOK hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProc, hInstance, 0);
    if (hKeyboardHook == NULL) {
        std::cerr << "Failed to install keyboard hook\n" << GetLastError();
        return 1;
    }

我尝试使用 Chatgpt 来解决这个问题,但它只会使代码变得更糟,并指示我实现一个循环,该循环将拦截来自钩子链的消息,尝试这样做,但陷入了无限循环,并且屏幕上没有显示任何内容尝试执行我的程序。

c++ windows winapi hook keyhook
1个回答
0
投票

这是一些繁忙循环等待按键的示例代码。通常,这些类型的循环被认为是不好的,但我认为对于这种类型的应用程序来说是可以的。

ReadConsoleInput
会阻塞,直到读取输入记录,因此它不会不断循环。

注意:我刚刚写了这篇文章,所以它还远未完成。另外,我从来没有写过这样的程序,所以可能有更好的方法。

#include <Windows.h>
#include <string>

// Reads a key from the keyboard. Blocks until user enters a key.
KEY_EVENT_RECORD getchar(HANDLE con)
{
    DWORD number_of_events;
    INPUT_RECORD record;

    while (true)
    {
        ReadConsoleInput(con, &record, 1, &number_of_events);
        if (record.EventType == KEY_EVENT && record.Event.KeyEvent.bKeyDown)
        {
            return record.Event.KeyEvent;
        }
    }
}

// Print to console
void echo(char c, COORD& pos, HANDLE con)
{
    SetConsoleCursorPosition(con, pos);
    WriteConsole(con, &c, 1, nullptr, nullptr);
    pos.X += 1;
}
void echo(const std::string& msg, COORD& pos, HANDLE con)
{
    SetConsoleCursorPosition(con, pos);
    WriteConsole(con, msg.c_str(), msg.length(), nullptr, nullptr);
    pos.X += msg.length();
}

int main()
{
    HANDLE con_in = GetStdHandle(STD_INPUT_HANDLE);
    HANDLE con_out = GetStdHandle(STD_OUTPUT_HANDLE);
    if (con_in == INVALID_HANDLE_VALUE || con_out == INVALID_HANDLE_VALUE) return -1;

    COORD pos{ 0 };      // Position to type next char
    COORD cmd_pos{ 0 };  // Position to type next command input char

    CONSOLE_SCREEN_BUFFER_INFO csbi;
    GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
    cmd_pos.Y = csbi.dwSize.Y - 1;

    // Cursor position. It can be either pos or cmd_pos.
    COORD *current = &pos;

    while (true)
    {
        KEY_EVENT_RECORD key = getchar(con_in);
        // Escape key? Enter command mode
        if (key.wVirtualKeyCode == 27) {
            current = &cmd_pos;
            echo("COMMAND: ", *current, con_out);
        }
        // New line
        else if (key.wVirtualKeyCode == 13) {
            // In text entry mode?
            if (current == &pos) {
                pos.X = 0;
                pos.Y += 1;
            }
            // In command mode?
            else {
                current = &pos;
                cmd_pos.X = 0;
                // TODO: Clear the command....
                // TODO: Execute command.....
            }
            SetConsoleCursorPosition(con_out, *current);
        }
        else {
            echo(key.uChar.AsciiChar, *current, con_out);
        }
    }
    return 0;
}

另一种选择是将

getchar
函数放在单独的线程中,并使用事件来提醒主程序有可用的输入。

© www.soinside.com 2019 - 2024. All rights reserved.