study windows hooks, several problems of performances and keys detection

This is the forum for miscellaneous technical/programming questions.

Moderator: 2ffat

Post Reply
mark_c
BCBJ Master
BCBJ Master
Posts: 246
Joined: Thu Jun 21, 2012 1:13 am

study windows hooks, several problems of performances and keys detection

Post by mark_c »

wrote this code to study windows hooks but it has several problems. Some problems are of response speed, while others are of correctness of the output. For example, if I press the keys with code: (90="Z",67="C") together and holding them down I also press the key (88="X"), its pressure is not detected.

Ultimately what I want to do is capture the pressure of multiple keys pressed in any order and in any way.

Code: Select all

int test = 0;
void __fastcall TForm1::Button1Click(TObject *Sender)
{
        bAbort = false;
        Label1->Caption = "Started.....";

        hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD,
                                         (HOOKPROC)KeyboardProc,
                                         (HMODULE)GetModuleHandle(NULL),
                                          GetCurrentThreadId() );

// useless code Mark_c
/*
        MSG message;
       
        while(GetMessage(&message, NULL, 0, 0) > 0)
        {
                TranslateMessage( &message );
                DispatchMessage( &message );
                Caption=test;
                if(bAbort == true) break;
        }

        UnhookWindowsHookEx(hKeyboardHook);
*/
}
//---------------------------------------------------------------------------

LRESULT CALLBACK KeyboardProc( int nCode, WPARAM wParam, LPARAM lParam )
{
        unsigned char keycode[]={90, 88, 67, 86, 66, 78, 77, 188};
                
        for(int i=0; i < sizeof(keycode); i++)
        {
                if(GetAsyncKeyState(keycode[i]))                
                    test++;
        }

        return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}
Last edited by mark_c on Tue Nov 17, 2020 7:35 am, edited 1 time in total.
The mediocre teacher tells. The good teacher explains. The superior teacher demonstrates.
rlebeau
BCBJ Author
BCBJ Author
Posts: 1726
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA
Contact:

Re: study windows hooks, several problems of performances and keys detection

Post by rlebeau »

mark_c wrote: Tue Nov 03, 2020 1:07 am Some problems are of response speed
If speed is an issue, consider using the Raw Input API.
mark_c wrote: Tue Nov 03, 2020 1:07 am if I press the keys with code: (90="Z",67="C")
Those are ASCII CHARACTER CODES, not KEYBOARD KEY CODES. But, it just happens that VIRTUAL KEY CODES for ASCII letters/numbers do match their ASCII character codes. But not all ASCII characters do. So be aware that there is a distinction you need to make.
mark_c wrote: Tue Nov 03, 2020 1:07 am together and holding them down I also press the key (88="X"), its pressure is not detected.
That is because your code is managing the hook incorrectly to begin with.
mark_c wrote: Tue Nov 03, 2020 1:07 am Ultimately what I want to do is capture the pressure of multiple keys pressed in any order and in any way.
First, you are hooking only your own app's main UI thread. There are easier ways to handle keyboard input for the main UI thread without using a keyboard hook at all. You can use the TApplication(Events)::OnMessage event, handling WM_KEY(DOWN|UP) and WM_CHAR messages as needed, eg:

Code: Select all

void __fastcall TForm1::TForm1(TComponent *Owner)
    : TForm(Owner)
{
    Application->OnMessage = &AppMessage;
}

void __fastcall TForm1::~TForm1()
{
    Application->OnMessage = NULL;
}

void __fastcalll TForm1::AppMessage(MSG &Msg, bool &Handled)
{
    switch (Msg.message)
    {
        case WM_KEYDOWN:
        case WM_KEYUP:
            // use Msg.wParam and Msg.lParam as needed...
            // note that Msg.wParam is a VIRTUAL KEY CODE...
            break;

        case WM_CHAR:
            // use Msg.wParam and Msg.lParam as needed...
            // note that Msg.wParam is a TRANSLATED CHARACTER CODE...
            break;
    }
}
Or, you can simply set the Form's KeyPreview property to true, and then use the Form's OnKey(Down|Up|Press) events, eg:

Code: Select all

void __fastcall TForm1::TForm1(TComponent *Owner)
    : TForm(Owner)
{
    KeyPreview = true;
}

void __fastcalll TForm1::FormKeyDown(TObject* Sender, Word &Key, TShiftState Shift)
{
    // use Key and Shift as needed...
    // note that Key is a VIRTUAL KEY CODE...
}

void __fastcalll TForm1::FormKeyUp(TObject* Sender, Word &Key, TShiftState Shift)
{
    // use Key and Shift as needed...
    // note that Key is a VIRTUAL KEY CODE...
}

void __fastcalll TForm1::FormKeyDown(TObject* Sender, WideChar &Key)
{
    // use Key as needed...
    // note that Key is a TRANSLATED CHARACTER CODE...
}
Second, you are running your own manual message loop that is not a valid VCL message loop. If you must use your own loop, then at least use the TApplication::HandleMessage()/TApplication::ProcessMessages() method instead of using TranslateMessage()/DispatchMessage() directly, so that VCL activity is handled properly. However, do note that keyboard hooks don't use actual messages, the hook callback will be called inside of GetMessage() itself without returning a message to your code, so your loop will not handle the updated 'test' value in a timely manner, which is probably what is confusing you. Do your UI updates in the hook callback itself, eg:

Code: Select all

int test = 0;
bool bAbort = false;
HHOOK hKeyboardHook = NULL;

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    bAbort = false;
    Label1->Caption = "Started.....";

    hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD,
                                         (HOOKPROC)KeyboardProc,
                                         (HMODULE)GetModuleHandle(NULL),
                                          GetCurrentThreadId() );
    if (hKeyboardHook)
    {
        do
        {
            Application->HandleMessage();
        }
        while (!bAbort && !Application->Terminated);

        UnhookWindowsHookEx(hKeyboardHook);
        hKeyboardHook = NULL;
    }
}

void __fastcall TForm1::Button2Click(TObject *Sender)
{
    bAbort = true;
}
//---------------------------------------------------------------------------

// note that these are VIRTUAL KEY CODES...
static const unsigned char keycodes[] = {'Z', 'X', 'C', 'V', 'B', 'N', 'M', VK_OEM_COMMA};
static const int num_keycodes = sizeof(keycode)/sizeof(keycode[0]);

LRESULT CALLBACK KeyboardProc( int nCode, WPARAM wParam, LPARAM lParam )
{                
    if (nCode == HC_ACTION)
    {
        for(int i = 0; i < num_keycodes; ++i)
        {
            if (GetAsyncKeyState(keycodes[i]) < 0)
            {
                ++test;
            }
        }
        Form1->Caption = test;
    }

    return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}
But better, just get rid of the manual loop altogether and let the normal VCL message loop in TAppliction::Run() handle everything for you. Just install the hook and then exit, letting execution return to the main message loop, eg:

Code: Select all

int test = 0;
HHOOK hKeyboardHook = NULL;

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    Label1->Caption = "Started.....";

    hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD,
                                         (HOOKPROC)KeyboardProc,
                                         (HMODULE)GetModuleHandle(NULL),
                                          GetCurrentThreadId() );
}

void __fastcall TForm1::Button2Click(TObject *Sender)
{
    if (hKeyboardHook)
    {
        UnhookWindowsHookEx(hKeyboardHook);
        hKeyboardHook = NULL;
    }
}
//---------------------------------------------------------------------------

// note that these are VIRTUAL KEY CODES...
static const unsigned char keycodes[] = {'Z', 'X', 'C', 'V', 'B', 'N', 'M', VK_OEM_COMMA};
static const int num_keycodes = sizeof(keycode)/sizeof(keycode[0]);

LRESULT CALLBACK KeyboardProc( int nCode, WPARAM wParam, LPARAM lParam )
{                
    if (nCode == HC_ACTION)
    {
        for(int i = 0; i < num_keycodes; ++i)
        {
            if (GetAsyncKeyState(keycodes[i]) < 0)
            {
                ++test;
            }
        }
        Form1->Caption = test;
    }

    return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}
Third, is there a reason why you are using GetAsyncKeyState() rather than looking at the key data that the hook gives you in the wParam and lParam parameters?

Code: Select all

int test = 0;
HHOOK hKeyboardHook = NULL;

LRESULT CALLBACK KeyboardProc( int nCode, WPARAM wParam, LPARAM lParam )
{                
    if (nCode == HC_ACTION)
    {
        // note that wParam is a VIRTUAL KEY CODE...
        switch (wParam)
        {
            case 'Z':
            case 'X':
            case 'C':
            case 'V':
            case 'B':
            case 'N':
            case 'M':
            case VK_OEM_COMMA:
                ++test;
                Form1->Caption = test;
                break;
        }
    }

    return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}
Remy Lebeau (TeamB)
Lebeau Software
mark_c
BCBJ Master
BCBJ Master
Posts: 246
Joined: Thu Jun 21, 2012 1:13 am

Re: study windows hooks, several problems of performances and keys detection

Post by mark_c »

thanks Remy as always.
If you try this version you will see that if you always keep pressed the keys Z + C and only then press X without leaving Z + C, you will see as output on the Caption s = "ZC" and never s = "ZXC", as if keeping Z keys always pressed + C inhibits the reading of the X key (in other words, the pressure of the X key is not detected).

Code: Select all

HHOOK hKeyboardHook = NULL;

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    Label1->Caption = "Started.....";

    hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD,
                                         (HOOKPROC)KeyboardProc,
                                         (HMODULE)GetModuleHandle(NULL),
                                          GetCurrentThreadId() );
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button2Click(TObject *Sender)
{
    UnhookWindowsHookEx(hKeyboardHook);
}
//---------------------------------------------------------------------------

static const unsigned char keycodes[] = {'Z', 'X', 'C', 'V', 'B', 'N', 'M', VK_OEM_COMMA};
static const int num_keycodes = sizeof(keycodes)/sizeof(keycodes[0]);

LRESULT CALLBACK KeyboardProc( int nCode, WPARAM wParam, LPARAM lParam )
{             
    if (nCode == HC_ACTION)
    {
        AnsiString s;
        for(int i = 0; i < num_keycodes; ++i)
        {
            if (GetAsyncKeyState(keycodes[i]) < 0)
            {
                s+=keycodes[i];
            }
        }
        Form1->Caption = s;
    }

    return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}
Third, is there a reason why you are using GetAsyncKeyState() rather than looking at the key data that the hook gives you in the wParam and lParam parameters?
yes, since I have no experience with this type of projects (keyboard management), I learned it by reading around the net and my need is to read the pressure of more than one key at a time, even ten simultaneously and quickly
Last edited by mark_c on Wed Nov 04, 2020 11:34 am, edited 3 times in total.
The mediocre teacher tells. The good teacher explains. The superior teacher demonstrates.
User avatar
Emily W. Haley
Posts: 3
Joined: Wed May 13, 2020 7:33 pm

Re: study windows hooks, several problems of performances and keys detection

Post by Emily W. Haley »

Hi, I have issues with too. But I can't hook global windows touch WM_TOUCH using GETMESSAGE hook or anything else. I am trying to hook touch input and disable some touch points if they are inside a specific region on the screen is that even possible?
rlebeau
BCBJ Author
BCBJ Author
Posts: 1726
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA
Contact:

Re: study windows hooks, several problems of performances and keys detection

Post by rlebeau »

mark_c wrote: Tue Nov 03, 2020 12:57 pm If you try this version you will see that if you always keep pressed the keys Z + C and only then press X without leaving Z + C, you will see as output on the Caption s = "ZC" and never s = "ZXC", as if keeping Z keys always pressed + C inhibits the reading of the X key (in other words, the pressure of the X key is not detected).
Well yeah, because you are using GetAsyncKeyState(), which is ASYNCHRONOUS. By the time you actually call it, the X key might not actually be down at that exact moment. That is why I mentioned earlier that you SHOULD be using the key data that the hook actually provides to you in the wParam and lParam parameters. Then you won't miss state changes for each key event.

Try this instead:

Code: Select all

HHOOK hKeyboardHook = NULL;
System::String KeysDown;

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    Label1->Caption = "Started.....";

    hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD,
                                         (HOOKPROC)KeyboardProc,
                                         (HMODULE)GetModuleHandle(NULL),
                                          GetCurrentThreadId() );
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button2Click(TObject *Sender)
{
    UnhookWindowsHookEx(hKeyboardHook);
}
//---------------------------------------------------------------------------

static const UCHAR keycodes[] = {'Z', 'X', 'C', 'V', 'B', 'N', 'M', VK_OEM_COMMA};
static const DWORD num_keycodes = sizeof(keycodes)/sizeof(keycodes[0]);

LRESULT CALLBACK KeyboardProc( int nCode, WPARAM wParam, LPARAM lParam )
{             
    if (nCode == HC_ACTION)
    {
        for(DWORD i = 0; i < num_keycodes; ++i)
        {
            if (wParam == keycodes[i])
            {
                System::Char ch = (wParam == VK_OEM_COMMA) : _D(',') ? System::Char(wParam);
                int idx = KeysDown.Pos(ch);

                if (lParam & 0x80000000)
                {
                    // key is being released
                    if (idx != 0) KeysDown.Delete(idx, 1);
                }
                else
                {
                    // key is being pressed
                    if (idx == 0) KeysDown += ch;
                }

                Form1->Caption = KeysDown;
                break;
            }
        }
    }

    return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}
But, again, this would be much better haadled using the TApplication::OnMessage or TForm::OnKey(Down|Up) events, instead of a keyboard hook at all.
mark_c wrote: Tue Nov 03, 2020 12:57 pm my need is to read the pressure of more than one key at a time, even ten simultaneously and quickly
Instead of using GetAsyncKeyState() for that, I would keep an array of the desired keys, and then use the hook/events to update that array as their states change.
Remy Lebeau (TeamB)
Lebeau Software
rlebeau
BCBJ Author
BCBJ Author
Posts: 1726
Joined: Wed Jun 01, 2005 3:21 am
Location: California, USA
Contact:

Re: study windows hooks, several problems of performances and keys detection

Post by rlebeau »

Emily W. Haley wrote: Tue Nov 03, 2020 6:20 pm I can't hook global windows touch WM_TOUCH using GETMESSAGE hook or anything else.
How to hook the WM_TOUCH message?
There is no good way to catch touch input globally on Windows 7. As Bob mentioned, windows need to be specifically registered to receive touch input and this isn't something that can be safely done for other applications' windows. Even if you can get the WM_TOUCH message, you won't be able to get the actual touch data via GetTouchInputInfo safely.
Emily W. Haley wrote: Tue Nov 03, 2020 6:20 pm I am trying to hook touch input and disable some touch points if they are inside a specific region on the screen is that even possible?
Not that I'm aware of.
Remy Lebeau (TeamB)
Lebeau Software
Post Reply