Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keyboard Hook problems

I am doing a voice chat application which uses a push-to-talk key. I have done a hook so it will register push-to-talk outside application too.

HHOOK hHook = SetWindowsHookEx(WH_KEYBOARD_LL,(HOOKPROC)pushtotalk,0,0);



LRESULT CALLBACK pushtotalk(int key, WPARAM wParam,LPARAM lParam) {
if (key < 0) {
    return (CallNextHookEx(hook,key,wParam,lParam));
}
else if (connected) {
    KBDLLHOOKSTRUCT* kbdll  = (KBDLLHOOKSTRUCT*)lParam;
    if (kbdll ->vkCode == 75 && wParam == WM_KEYDOWN) {
        MessageBox(mainhWnd,"KEYSTART","KEYSTART",0);
    }
    else if (kbdll ->vkCode == 75 && wParam == WM_KEYUP) {
        MessageBox(mainhWnd,"KEYSTOP","KEYSTOP",0);

    }
}

return (CallNextHookEx(hook,key,wParam,lParam));
}

Problems;

1) Sometimes, (for example the first execution of the proc in the application), the proc causes a 5 sec system freeze before continuing. Why?

2) The hook only works on process that were started before my application started, if I start a text program after starting my application, the hooks wont register. Is there a fix for this?

3) If I hold down the key for ~3 seconds, alot of MessageBoxes shows obviously, but after that, the proc will never register another key being pushed down, so I guess I somehow gets disconnected from the hook chain?

Cheers

EDIT: Here's the main message loop for the application

LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch(message) {
    case WM_COMMAND:
        switch (LOWORD(wParam)) {
            case ID_MENU_EXIT:
                SendMessage(hWnd,WM_CLOSE,0,0);
                break;

            case ID_MENU_PREFERENCES:
                voiceManager->send((void*) "1");
                break;

            case ID_BUTTON_CONNECT:
                onConnect(hWnd);
                break;

            case ID_BUTTON_DISCONNECT:
                onDisconnect(hWnd);
                break;

            case ID_BUTTON_SEND:
                onSendText(hWnd);
                break;

            default:
                break;
        }
        break;
    case SOCKET_TCP:
        switch (lParam) {
            case FD_READ:
                {
                // Disable repeated FD_READ call while we process message 
                WSAAsyncSelect(wParam,hWnd,SOCKET_TCP,   FD_WRITE | FD_ACCEPT  | FD_CLOSE);

                // first four bytes is packet size
                // second four bytes are used to identify type of msg
                char* psize = (char*)malloc(5);
                char* ptype = (char*)malloc(5);
                psize[4] = '\0';
                ptype[4] = '\0';

                recv(wParam,psize,4,0);
                recv(wParam,ptype,4,0);

                // allocate memory for the buffer 
                int size_to_recv = atoi(psize);      
                char* textbuff = (char*)malloc(size_to_recv);

                // receive 
                int i = size_to_recv;
                while (i > 0) {
                    int read = recv(wParam,textbuff,i,0);
                    i = i - read;
                }

                // handle msg depending on type
                switch(identifyMsg(ptype)) {
                    case 1:
                        // handle 'text' msg
                        onReadText(hWnd,textbuff);
                        break;

                    case 2:
                        // handle 'name' msg
                        onReadName(hWnd,textbuff);
                        break;
                    case 3:
                        // handle 'list' msg
                        onReadList(hWnd,textbuff);
                        break;
                    case 4:
                        // handle 'remv' msg
                        onReadRemv(hWnd,textbuff,size_to_recv);
                        break;
                    case 5:
                        // handle 'ipad' msg -- add ip
                        voiceManager->addParticipant(inet_addr(textbuff));
                        break;
                    case 6:
                        // handle 'iprm' msg -- remove ip 
                        voiceManager->removeParticipant(inet_addr(textbuff));
                        break;

                    default:
                        break;
                }

                // re-enable FD_READ
                WSAAsyncSelect(wParam,hWnd,SOCKET_TCP,   FD_WRITE | FD_ACCEPT | FD_READ | FD_CLOSE);

                // free resources
                free(psize);
                free(ptype);
                free(textbuff);
                break;
                }

            case FD_WRITE:
                break;

            case FD_CONNECT:
                break;

            case FD_CLOSE:
                onDisconnect(hWnd);
                break;

            default:
            break;
        }
        break;



    case WM_PAINT:
        paintText(hWnd);
        break;

    case WM_DESTROY:
        shutdownConnection(hWnd);
        // reset window procs
        SetWindowLong(GetDlgItem(hWnd,ID_EDIT_SEND), GWL_WNDPROC,(LONG) OriginalEditProc);
        SetWindowLong(GetDlgItem(hWnd,ID_EDIT_IP), GWL_WNDPROC,(LONG) OriginalEditProc);
        PostQuitMessage(0);
        return 0;
        break;

    case WM_CLOSE:
        DestroyWindow(hWnd);
        break;

    default:
        break;
}


return DefWindowProc(hWnd, message, wParam, lParam);
}


LRESULT CALLBACK sendEditProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
if (message == WM_CHAR) {
    if (wParam == VK_RETURN) {
        onSendText(GetParent(hWnd));
        return 0;
    }
}
if (message == WM_KEYUP || message == WM_KEYDOWN) {
    if (wParam == VK_RETURN) {
        return 0;
    }
}
return CallWindowProc(OriginalEditProc, hWnd, message, wParam,lParam);
}

Where sendEditProc is a sub/superclass designed to intercept 'enter' keys when inside an edit control 'send' Does this help?

Here's the message loop; it's the standard so nothing fancy that could go wrong afaik :)

while (GetMessage(&msg, NULL,0,0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
like image 827
KaiserJohaan Avatar asked Jan 19 '11 00:01

KaiserJohaan


1 Answers

You're calling CallNextHookEx one too many times. If key < 0 return CallNextHookEx, otherwise return 0.

The problem you're seeing has to do with keyboard repeats and the MessageBox or MessageBeep methods being very, very expensive calls. Try this test:

HHOOK hHook;
BOOL bTalkEnabled = FALSE;

LRESULT CALLBACK pushtotalk(int key, WPARAM wParam, LPARAM lParam)
{
    if (key < 0)
        return CallNextHookEx(hHook, key, wParam, lParam);

    KBDLLHOOKSTRUCT* kbdll  = (KBDLLHOOKSTRUCT*)lParam;
    if (kbdll->vkCode == VK_F11)
    {
        BOOL bStarted = FALSE;
        BOOL bStopped = FALSE;

        if (wParam == WM_KEYDOWN)
        {
            if (!bTalkEnabled)
            {
                bStarted = TRUE;
                bTalkEnabled = TRUE;
            }
        }
        else if (wParam == WM_KEYUP)
        {
            if (bTalkEnabled)
            {
                bStopped = TRUE;
                bTalkEnabled = FALSE;
            }
        }

        if (bStarted)
            OutputDebugString(L"Pushed\r\n");
        if (bStopped)
            OutputDebugString(L"Released\r\n");
    }

    return 0;
}

You can monitor the debug strings by running the app under the debugger (check the Output window), or you can get DebugView and watch that.

Notice that I do not check for connected as you did. You don't want to perform that check in the hook, do that outside the hook and only use the hook to determain if the key is pressed or not pressed.

like image 77
Tergiver Avatar answered Oct 07 '22 17:10

Tergiver