Конференция "WinAPI" » вопрос по keyboard хуку... [D7]
 
  • guest12 (23.08.14 00:26) [0]
    История следующая, есть кейборд хук, перехватывает нажатие клавиш. Когда он активен, у людей с неангл. языком, например португальским, отрубаются символы вроде ç ã, ó. Помогите плиз разобраться, в чем дело, я же не меняю wParam, lParam..



    ...

    KeyboardHook := SetWindowsHookEx(WH_KEYBOARD, @KeyboardProc, HInstance, 0);

    ...

    function KeyboardProc(Code : Integer; wParam : Word; lParam : Longint): Longint;
     stdcall;
    const
     SysKeys: set of Byte = [8, 9, 13, 27];
    var
     KeyDown: Boolean;
     KeyChar: Char;
     rslt: LongBool;
    begin
     if Code = HC_ACTION then
     begin
       KeyDown := (lParam and (1 shl 31)) = 0;
       if KeyDown then
       begin
         GetKeyboardState(Keyboard);
         SourceHandle := GetForegroundWindow;
         if not (wParam in SysKeys) and
           (ToAscii(WParam, LParam, Keyboard, @KeyChar, 0) > 0) then
             rslt := PostMessage(DestHandle, WM_KEYHOOK, SourceHandle, Ord(KeyChar))
         else
           rslt := PostMessage(DestHandle, WM_EXKEYHOOK, SourceHandle, lParam);
                 {$IFDEF DEBUG}
         if not rslt then
           WriteLogToMyDocs('PostMessage failed, getlasterror='+IntToStr(GetLastError));
           {$ENDIF}
       end;
     end;
     Result := CallNextHookEx(MouseHook, Code, wParam, lParam);
    end;


  • brother_irk (23.08.14 09:49) [1]
    Удалено модератором
  • Игорь Шевченко © (23.08.14 10:43) [2]
    Не вызываешь предыдущий хук
  • guest12 (23.08.14 12:35) [3]
    сорри, тут  Result := CallNextHookEx(MouseHook, Code, wParam, lParam); ошибка, должно ж быть  CallNextHookEx(KeyboardHook....

    в этом ошибка?

    про предыдущий хук не видел в примерах, видел только про CallNextHookEx который обычно вызывают в начале обработчика, как здесь http://www.swissdelphicenter.ch/torry/showcode.php?id=1722

    и почему бага касается только спец символов?
  • Игорь Шевченко © (23.08.14 23:15) [4]
    Виноват, ошибка в объявлении функции

    вместо


    > function KeyboardProc(Code : Integer; wParam : Word; lParam
    > : Longint): Longint;
    >  stdcall;


    следует писать

    function KeyboardProc(Code : Integer; wParam : WPARAM; lParam : LPARAM): LRESULT; stdcall;
  • guest12 (26.08.14 23:09) [5]
    кстати, на рсдн ответили следующее:  "У меня такое было. Оказалось, что GetKeyboardState или ToAscii (точно не помню какая из двух) имеет некое внутреннее состояние и будучи вызванной в контексте клавиатурного хука, это состояние портит. "

    но подробностей нет) может, тут кого наведет на решение...
  • Leonid Troyanovsky © (27.08.14 11:28) [6]

    > guest12   (26.08.14 23:09) [5]

    > вызванной в контексте клавиатурного хука, это состояние
    > портит. "но подробностей нет

    Не знаю, кто портит, но ToAscii  может вернуть хоть 2
    (для диакритических символов).
    Т.е., KeyChar, например, д.б. массивом.

    Кроме того, непонятно что есть Keyboard,
    а также  валиден ли MouseHook (лучше ли он нуля).

    --
    Regards, LVT.
  • guest12 (28.08.14 01:45) [7]
    тут вот говорят, давно известный косяк ToAscii и надо его 2 раза вызывать.. будем пробовать)
  • guest (28.08.14 01:45) [8]
  • guest12 (16.02.15 16:54) [9]
    двойной вызов не помог, мне некритично не получать спец символы в перехвате, но критично, что это влияет на систему - почему это происходит??
  • guest12 (17.02.15 00:42) [10]
    переход на ToUnicode тоже не помог, проблема с '' вместо ç ã, ó осталась...:


    function KeyboardProc(Code : Integer; wParam : WPARAM; lParam : LPARAM): LRESULT; stdcall;
    const
     SysKeys: set of Byte = [8, 9, 13, 27];
    var
     KeyDown: Boolean;
     KeyChar: Char;
     rslt: LongBool;
     ta: integer;
     us: widestring;

       UResult       : Integer;

     function VKeytoWideString (Key : Word) : WideString;
     var
       WBuff         : array [0..255] of WideChar;
       KeyboardState : TKeyboardState;

     begin
       Result := '';
       GetKeyBoardState (KeyboardState);
       ZeroMemory(@WBuff[0], SizeOf(WBuff));
       UResult := ToUnicode(key, MapVirtualKey(key, 0), KeyboardState, WBuff, Length(WBuff), 0);
       if UResult > 0 then
         SetString(Result, WBuff, UResult)
       else if UResult = -1 then
         Result := WBuff;
     end;

    begin
     if Code = HC_ACTION then
     begin
       KeyDown := (lParam and (1 shl 31)) = 0;
       if KeyDown then
       begin                            
         WriteLogToMyDocs('Enter: Code = ' + inttostr(Code) + ' wParam = ' + inttostr(wParam) + ' lParam = ' + inttostr(lParam));

         SourceHandle := GetForegroundWindow;

         WriteLogToMyDocs('before VKeytoWideString');
         try
         us := VKeytoWideString(WParam);
         WriteLogToMyDocs('us = ' + us);
         except
           on e: exception do
           WriteLogToMyDocs(e.message);
         end;
         WriteLogToMyDocs('after VKeytoWideString');

         try
         KeyChar := AnsiChar(us[1]);
         except
           on e: exception do
           WriteLogToMyDocs(e.message);
         end;

         WriteLogToMyDocs('us = ' + us + ' KeyChar = ' + KeyChar);

         if not (wParam in SysKeys) and (UResult > 0) then
             rslt := PostMessage(DestHandle, WM_KEYHOOK, SourceHandle, Ord(KeyChar))
         else
           rslt := PostMessage(DestHandle, WM_EXKEYHOOK, SourceHandle, lParam);

         if not rslt then
           WriteLogToMyDocs('PostMessage failed, getlasterror='+IntToStr(GetLastError));

           WriteLogToMyDocs('Exit: Code = ' + inttostr(Code) + ' wParam = ' + inttostr(wParam) + ' lParam = ' + inttostr(lParam));
       end;
     end;
     Result := CallNextHookEx(KeyboardHook, Code, wParam, lParam);
    end;

  • guest12 (17.02.15 01:02) [11]
    эксперименты показывают, что к примеру добавление вызова VKeytoWideString(75) сразу порождает баг в системе, если же его убрать - всё ок)

    при этом, вызов GetKeyboardState(Keyboard) не влияет на данную багу...

    что делать?)
  • Leonid Troyanovsky © (17.02.15 08:44) [12]

    > guest12   (17.02.15 01:02) [11]

    > что делать?)

    But  twice?

    --
    Regards, LVT.
  • guest12 (17.02.15 10:31) [13]

    > But  twice?


    двойной вызов ToUnicode (либо двойной ToAscii) позволяет при наборе 'e вместо '' выдать системе e без диакритического знака, что также является багой..
  • guest12 (17.02.15 13:11) [14]
    покурил статьи...  
    http://www.siao2.com/2006/04/06/569632.aspx
    http://www.siao2.com/2005/01/19/355870.aspx

    There are two ways to work around this:

    1) You can keep calling ToUnicode with the same info until it is cleared out and then call it one more time to put the state back where it was if you had never typed anything, or

    2) You can load all of the keyboard info ahead of time and then when they type information you can look up in your own info cache what the keystrokes mean, without having to call APIs later.


    первый солюшен не совсем ясен, при каких условиях и значениях ToUnicode\ToAscii делать повторные вызовы?
  • guest12 (17.02.15 13:34) [15]
    под делфи есть такое?)


    ======== some code:
       LRESULT CALLBACK MyKeyboardProc(int ccode, WPARAM wParam, LPARAM lParam)
       {
       if (ccode == HC_ACTION)
       {
       KBDLLHOOKSTRUCT *pkbdllhook = (KBDLLHOOKSTRUCT *)lParam;
       HKL dwhkl = 0;
       BYTE dbKbdState[256];
       TCHAR szCharBuf[32];
       static KBDLLHOOKSTRUCT lastState = {0}
    ;
       GetKeyboardState(dbKbdState);
       dwhkl = GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow(), NULL));
       if(ToAsciiEx(pkbdllhook->vkCode, pkbdllhook->scanCode, dbKbdState, (LPWORD)szCharBuf, 0, dwhkl) == -1)
       {
       //Save the current keyboard state.
       lastState = *pkbdllhook;
       //You might also need to hang onto the dbKbdState array... I'm thinking not.
       //Clear out the buffer to return to the previous state - wait for ToAsciiEx to return a value other than -1 by passing the same key again. It should happen after 1 call.
       while(ToAsciiEx(pkbdllhook->vkCode, pkbdllhook->scanCode, dbKbdState, (LPWORD)szCharBuf, 0, dwhkl) <0);
       }

       else
       {
       //Do something with szCharBuf here since this will overwrite it...
       //If we have a saved vkCode from last call, it was a dead key we need to place back in the buffer.
       if(lastState.vkCode != 0)
       {
       //Safest to just clear this.
       memset(dbKbdState, 0, 256);
       //Put the old vkCode back into the locale'
    s buffer.
       ToAsciiEx(lastState.vkCode, lastState.scanCode, dbKbdState, (LPWORD)szCharBuf, 0, dwhkl);
       //Set vkCode to 0, we can use this as a flag as a vkCode of 0 is invalid.
       lastState.vkCode = 0;
       }

       }
       }
       return (CallNextHookEx(hHook, ccode, wParam, lParam));
       }
       ============= end code

  • guest12 (17.02.15 13:57) [16]
    перевел в Delphi, следующим образом:

       

    const
    SysKeys: set of Byte = [8, 9, 13, 27];
    var
    KeyDown: Boolean;
    KeyChar: Char;
    rslt: LongBool;
    Keyboard, dbKbdState: TKeyboardState;
    ta: integer;
    dwhkl: HKL;
    lastState: TMessage;
    begin

    GetKeyboardState(dbKbdState);
       dwhkl := GetKeyboardLayout(GetWindowThreadProcessId(GetForegroundWindow()));
       if(ToAsciiEx(Msg.WParam, Msg.LParam, dbKbdState, @KeyChar, 0, dwhkl) = -1) then
       begin
         //Save the current keyboard state.
         lastState := Msg;
         //You might also need to hang onto the dbKbdState array... I'm thinking not.
         //Clear out the buffer to return to the previous state - wait for ToAsciiEx to return a value other than -1 by passing the same key again. It should happen after 1 call.
         while(ToAsciiEx(Msg.WParam, Msg.LParam, dbKbdState, @KeyChar, 0, dwhkl) <0) do
           ;
       end

       else
       begin
         //Do something with szCharBuf here since this will overwrite it...
         //If we have a saved vkCode from last call, it was a dead key we need to place back in the buffer.
         if(lastState.WParam <> 0) then
         begin
           //Safest to just clear this.
           FillChar(dbKbdState, 0, 256);
           //Put the old vkCode back into the locale'
    s buffer.
           ToAsciiEx(lastState.WParam, lastState.LParam, dbKbdState, @KeyChar, 0, dwhkl);
           //Set vkCode to 0, we can use this as a flag as a vkCode of 0 is invalid.
           lastState.WParam := 0;
         end
       end;
    end;



    в итоге проблема осталась - диакритические знаки не отображаются (выдает просто а вместо ' над a)
  • guest12 (17.02.15 19:59) [17]
    предлагают еще такое решение, но не пойму где использовать такой цикл в хуке..?


    The problem probably lies with ToAsciiEx.
    It takes keystrokes and converts it to (ascii) character codes.

    When using ToAsciiEx in a global hook proc, this messes up the handling of dead key characters in the application that is being hooked ..
    The reason is that ToAsciiEx stores (remembers) the dead key when pressed and does not return a character.
    When a 2nd key is pressed, ToAsciiEx takes the stored dead key, combines that with the new key and returns the combined character.
    If a hook proc calls ToAsciiEx, the hooked application will also call ToAsciiEx (Actually TranslateMessage does this in the applications main message loop).
    The fact that ToAsciiEx is called twice to process the same keystrokes messes up the dead key character combination, because the first ToAsciiEx that is called will eat the dead key that was stored and the 2nd ToAsciiEx will no longer find a dead key character stored and will therefore produce an incorrect ascii character ...
    We basically confuse ToAsciiEx by calling it twice to process the same keystrokes: once in the global hook and once in the hooked application .
    Ofcourse, in English, these special characters like "&#224;" do not exist and this problem does not occur there. But in most European languages it does.
    I have found a number of attempts on the internet to work around this problem, but none of them works as it should.

    A method that works is to simply trap the WM_CHAR message in a global hook.
    A WM_CHAR message carries complete characters like &#233;, &#241;, etc.
    A WM_CHAR message is generated by the API function TranslateMessage that takes the keystrokes (WM_KEYDOWN etc.)
    and translates the virtual key codes into an ascii character (most likely by calling ToAsciiEx internally).
    TranslateMessage is called in an applications main message loop like this:
    Code:
       DO WHILE GetMessage(Msg, %NULL, 0, 0)
           TranslateMessage Msg
           DispatchMessage Msg
       LOOP
    Strangely enough, I have read that there are applications that do NOT call TranslateMessage, and use a message loop like:
    Code:
       DO WHILE GetMessage(Msg, %NULL, 0, 0)
           DispatchMessage Msg
       LOOP
    Like this, no WM_CHAR messages are generated and a global hook can not trap the characters ...
    However, keystroke messages like WM_KEYDOWN are still generated ..
    I read that Borland Delphi shows (or showed) this behaviour, though I myself have never encountered a program behaving like this.
    Apart from one I wrote myself to test.

    Hope this helps (a bit)...

    Kind regards

  • Leonid Troyanovsky © (18.02.15 09:10) [18]

    > guest12   (17.02.15 10:31) [13]

    Неспешно копаясь в msdn внезапно читаем:

    As ToUnicodeEx translates the virtual-key code, it also changes the state of the kernel-mode keyboard buffer. This state-change affects dead keys, ligatures, alt+numpad key entry, and so on. It might also cause undesired side-effects if used in conjunction with TranslateMessage (which also changes the state of the kernel-mode keyboard buffer).

    https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms646322(v=vs.85).aspx

    Что, видимо, заказывает пользование оных функций в глобальном хуке.
    Т.е., интепретировать ввод следует собс-ручно.

    --
    Regards, LVT.
 
Конференция "WinAPI" » вопрос по keyboard хуку... [D7]
Есть новые Нет новых   [118461   +17][b:0][p:0.005]