KeyboardHook - GetKeyState - problem z przechwytywaniem klawiszy

0

Witam,

mam problem z przechwytywaniem klawiszy, a mianowicie dotyczy on przechwytywania zdarzenia OnKeyDown i OnKeyUp oraz czasu jaki upłynął pomiędzy KeyDown a KeyUp.

Kod znajduje się w DLL:

function KeyboardHookProc(nCode : Integer; wParam : Integer; lParam : Integer) : Integer; stdcall; export;
var
  Hook : PKBDLLHOOKSTRUCT;
  KeyState : TKeyboardState;
  NewChar: array[0..1] of Char;
  i: boolean;
begin
  Hook := Pointer(lParam);
  case nCode of
    HC_ACTION :
      begin
        If ((Hook^.flags And LLKHF_UP) = 0) and not iMoveIt.getConfig.Hook_Paused Then
        begin
          FillChar(NewChar,2,#0);
          GetKeyboardState(KeyState);

          if Hook.vkCode = VK_ESCAPE then
          begin
            if GetKeyState(VK_ESCAPE)<>0 then
            begin
              Down := True;
              Start := GetTickCount;
              iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0,
                 0, '[ESC DOWN]');
            end else
            begin
              Down := False;
                stop := GetTickCount;

                iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0,
                   stop - start, '[ESC UP]');

          end;

Powyższy kod działa ale jeśli 2 razy wcisnę klawisz ESC. Jeśli tylko raz, to wykonuje mi ESC DOWN.
Jeśli macie pomysł to przy okazji w jaki sposób mogę zastosować powyższy kod, aby działał identycznie na pozostałe klawisze specjalne ESC, SHIFT, ALT, CTRL, ENTER

0

Według mnie coś dziwnie sprawdzasz to czy klawisz jest wciśnięty czy puszczony, przeanalizuj moje źródła w tym temacie pod linkiem poniżej. Kiedy będę w domu i zajrze tutaj po południu wrzucę kod aktualny, bo teraz nie mogę go wygooglować. Poza tym dodam, że nie ma konieczności stosowania tego typu hooka w dllce.

Przechwytywanie i modyfikacja klawiszy wysyłanych do aplikacji

0

Dzięki @olesio za odp. w Twoich źródłach widzę kilka ciekawych przykładów np. zamienianie klawiszy na string co wydaje się dość przydatne jeśli chodzi o moją aplikację, jednak albo źle szukałem, albo nie znalazłem tam wskazówek odnośnie mojego problemu mimo, że w tamtym wątku napisałeś "jak wykorzystać globalny Hook na WH_KEYBOARD_LL po to aby podmienić naciśnięcia klawiszy". Jeszcze raz przejrzę ten kod, bo przeglądałem go rano na szybko przed wyjściem do pracy.

Czekam na odp.

znalazłem w Twoim kodzie taki kod (po moich modyfikacjach):

type
  TKeyShiftState = set of (ssShift, ssAlt, ssCtrl);

function IsKeyHeldDown(vkCode : integer) : boolean;
function ReturnKeyShiftState(vkCode : Integer) : boolean;

implementation

function IsKeyHeldDown(vkCode : integer) : boolean;
begin
  Result := GetAsyncKeyState(vkCode) shr ((SizeOf(SHORT) * 8) - 1) <> 0;
end;

function ReturnKeyShiftState(vkCode : Integer) : boolean;
begin
  Result := false;
  if IsKeyHeldDown(vkCode) then
  begin
    Result := true;
  end;

end;

wewnątrz hooka robię:

          if Hook.vkCode = VK_ESCAPE then
          if (ReturnKeyShiftState(hook.vkCode) = true) and not down then
          begin
            down := True;
            Start := GetTickCount; // pobierz czas wcisniecia
              iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, 0, '[ESC DOWN]'); // dodaj info o wcisnieciu klawisza
          end else
          begin
            if down then
            begin
              down := false;
              stop := GetTickCount; // pobierz czas puszczenia
                                   
              iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, stop - start, '[ESC UP]'); // dodaj info o puszczeniu klawisza z delayem ile byl wcisniety
            end;
          end;

Ale efekt taki, że nie czeka na puszczenie klawisza tylko wykonuje mi procedure iMoveIt.AddAction w pętli ;/

Kolejna próba to (również z Twojego kodu):

function KeyboardHookProc(nCode : Integer; wParam : Integer; lParam : Integer) : Integer; stdcall; export;
var
  KD : TKeyData;
  KDPtr : ^TKeyData;
  Hook : PKBDLLHOOKSTRUCT;
  KeyState : TKeyboardState;
  NewChar: array[0..1] of Char;
  i: boolean;
begin
  Hook := Pointer(lParam);
  case nCode of
    HC_ACTION :
      begin
        If {((Hook^.flags And LLKHF_UP) = 0) and} not iMoveIt.getConfig.Hook_Paused Then
        begin
          FillChar(NewChar,2,#0);
          GetKeyboardState(KeyState);

          if (not PressedInProgress) then
          begin
            with KD do
            begin
              KeyCode := Hook.vkCode;
              IsKeyDown := Hook.Flags and LLKHF_UP = 0;
            end;

            stop := GetTickCount;
            iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, 0, '[KEY DOWN]');

            KDPtr := @KD;
            PressedInProgress := True;

            Start := GetTickCount;

            StartThread(KeyDownUpThreadProc, KDPtr);
            Result := 1;
            Exit;
          end;

          stop := GetTickCount;
          iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, stop - start, '[KEY UP]');

Też wykonuje 2 procedury pod rząd do momentu puszczenia klawisza.

0

Poradziłem sobie, poniżej kod:

użyto CASE

            VK_TAB:
              begin
                if (wParam = WM_KEYDOWN) then
                begin
                  if not TAB_WaitForUp then
                  begin
                    Start := GetTickCount;
                    iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, 0, '[TAB DOWN]');
                    TAB_WaitForUp := True;
                  end;
                end else
                begin
                  stop := GetTickCount;
                  iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, stop - start, '[TAB UP]');
                  TAB_WaitForUp := False;
                end;
              end;

            VK_LMENU, VK_RMENU, VK_MENU:
              begin
                if (wParam = WM_KEYDOWN) then
                begin
                  if not ALT_WaitForUp then
                  begin
                    Start := GetTickCount;
                    iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, 0, '[ALT DOWN]');
                    ALT_WaitForUp := True;
                  end;
                end else
                begin
                  stop := GetTickCount;
                  iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, stop - start, '[ALT UP]');
                  ALT_WaitForUp := False;
                end;
              end;

Teraz jednak jest inny problem. Dla klawiszy typu enter, esc, shift, backspace powyższy kod działa, ale dla klawisza ALT zwraca kod 157 i wchodzi mi od razu w ALT UP, tak jak by nie obsługiwał zdarzenia OnKeyDown czyli kod 160. Czym to może być spowodowane?

0

Zobacz załącznik do tego posta. Masz tam kod i exek. Trochę źle podpowiedziałem z wykrywaniem trzymania i puszczenia klawisza. Bo właśnie z klawiszami specjalnymi może być nieścisłość za pewne. A pisałem z pamięci. Wtedy nie mając dostępu do swoich kodów źródłowych. A na google nie mogłem znaleźć postów gdzie dołączałem to właśnie archiwum. Najlepiej to czy klawisz jest wciśnięty czy puszczony, wykrywać tak, jak tam zrobiłem. Zresztą podobnie chyba robisz u siebie, tylko że do tego dokładasz jakieś kombinację, które nie bardzo rozumiem. Czy taki najprostszy sposób, jak pokazałem w tym dołączonym kodzie, nie może być przez Ciebie z jakichś względów zastosowany? Ponieważ według mnie powinien się sprawdzić i zawsze zadziałać prawidłowo. A i fajnie, że przydał się Tobie mój moduł key_codes.pas. Powstał właśnie dla celów programu z: http://olesio.eu/amiga_swos_remapping_keys.exe pisanego dla społeczności onlineowego Amigowego SWOS'a. Gdyż niektórzy gracze grają na laptopach bez prawego Ctrl, a czasem bywają klawiatury gdzie wciskając niektóre skosy z prawym Ctrl'em system na to nie reaguje i przydaje się przemapowanie na przykład prawego Ctrl pod lewy Ctrl, co jednocześnie zapewnia obsługę pod prawym Alt'em.

EDIT: chyba powoli łapie, Ty chcesz przechwytywać sekwencje klawiszy i reagować na jedne w zależności od innych, jeżeli tak to może coś wyjaśni Tobie poniżśzy kod, który kiedyś znalazłem na google, a ktory blokuje pewne kombinacje systemowe.

unit lockhook;

interface

uses
  Windows, Messages;

const
  WH_KEYBOARD_LL = $000D;
  LLKHF_EXTENDED = $0001;
  LLKHF_INJECTED = $0010;
  LLKHF_ALTDOWN = $0020;
  LLKHF_UP = $0080;

type
  tagKBDLLHOOKSTRUCT = packed record
    vkCode : DWord;
    ScanCode : DWord;
    Flags : DWord;
    Time : DWord;
    dwExtraInfo : integer;
  end;
  KBDLLHOOKSTRUCT = tagKBDLLHOOKSTRUCT;
  PKBDLLHOOKSTRUCT = ^KBDLLHOOKSTRUCT;

var
  HKHook : HHook;
  
function LowLevelKeyboardProc(nCode : Integer; wParam : Integer; lParam : Integer) : Integer; stdcall; export;
procedure HookIt;
procedure UnHookIt;

implementation

procedure HookIt;
begin
  HKHook := SetWindowsHookEx(WH_KEYBOARD_LL, @LowLevelKeyboardProc, hInstance, 0);
end;

procedure UnHookIt;
begin
  UnHookWindowsHookEx(HKHook);
end;

function LowLevelKeyboardProc(nCode : Integer; wParam : Integer; lParam : Integer) : Integer; stdcall; export;
var
  Hook : PKBDLLHOOKSTRUCT;
  bControlKeyDown : boolean;
begin
  Hook := Pointer(lParam);
  case nCode of
    HC_ACTION :
      begin
        bControlKeyDown := ((GetAsyncKeyState(VK_CONTROL) shr ((Sizeof(SHORT) * 8) - 1)) <> 0);
      //Disable CTRL+ESC
        if (Hook^.vkCode = VK_ESCAPE) and (bControlKeyDown) then
        begin
          Result := 1;
        end
      //Disable ALT+TAB
        else if ((Hook^.flags and LLKHF_ALTDOWN) <> 0) and (Hook^.vkCode = VK_TAB) then
        begin
          Result := 1;
        end
      //Disable ALT+ESC
        else if ((Hook^.flags and LLKHF_ALTDOWN) <> 0) and (Hook^.vkCode = VK_ESCAPE) then
        begin
          Result := 1;
        end
      //Disable CTRL+ENTER
        else if (Hook^.vkCode = VK_RETURN) and (bControlKeyDown) then
        begin
          Result := 1;
        end
      //Disable Windows Key
        else if (Hook^.vkCode = VK_LWIN) or (Hook^.vkCode = VK_RWIN) then
        begin
          Result := 1;
        end
      //Disable All ALT Keys
        else if (Hook^.vkCode = VK_MENU) or (Hook^.vkCode = VK_LMENU) or (Hook^.vkCode = VK_RMENU) then
        begin
          Result := 1;
        end
        else
        begin
          Result := CallNextHookEx(HKHook, nCode, wParam, lParam);
        end;
      end;
  else
    begin
      Result := CallNextHookEx(HKHook, nCode, wParam, lParam);
    end;
  end;
end;

end.
0

Kod który zalaczyles rownież znalazłem. To co chciałem osiągnąć to udało się mi to osiągnąć za pomocą powyższego kodu. Dobrze zrozumiales mój kod bo chce wykonywać akcje w zależności od zdarzenia klawisza.

Problem jaki teraz mam to taki, ze po wcisnieciu klawisza ALT zachodzi zdarzenie od razu onkeyup i pomija onkeydown. Wiesz może co jest przyczyna ze vcode zwraca 157 a nie 160 czyli kod onkeydown?

0

Mogę się mylić. Ale jeżeli chodzi o Hook LowLevelowy globalny na klawiaturę to na przykład w przeciwnieństwie od Hooka LowLevelowego lokalnego - rożróznia osobno Controle i Alty czy Shifty. A OnKeyDown może jakoś inaczej reagowac. Może w tym tkwi problem. Chociaż pewności nie mam. Ale być może przyda się Tobie jeszcze taki kod, który kiedyś napisałem w wersji z obsługą HotKeyów i WM_HOTKEY na bazie cześći kodu z programu Cheat Engine. A który później przerobiłem na taki moduł global_hotkeys.pas, który wykorzystałem w jednym ze swoich pluginów do WinAmpa aby obsługiwał mi prawidłowo klawisze multimedialne kiedy włączone jest nowe ACDSee 15 na pierwszym planie. Gdyż inaczej nie reagowały. Dodam, że tam metodą z komunikatem WM_HOTKEY nie skutkowałą. Anyway, jeśli musisz tylko stwierdzić jakie klawisze zostały wciśnięte globalnie w całym systemie, a nie potrzebujesz ich blokować by na przykład podmienić na inne, to ja bym właśnie spróbował z GetAsyncKeyState w wątku. Tylko musisz się upewnić właśnie jak będzie z rozróżnianiem Alt/Ctrl na Lewy/Prawy. Bo z tego co widzę z szybkiego testu taki kod, jak poniżej sprawdza się i rozróżnia Ctrl'e. Poza tym dzięki zastosowaniu parametru Modifiers można ogarnąc wciskanie danego klawisza z dowolnym modyfikatorem (lewym lub prawym) jak Alt, Ctrl czu Shift. Lub wybranych czy wszystkich na raz. Oczywiście w kodzie poniżej KeyId to 1, a Modifiers nie są ustawione. Możesz też w razie potrzeby przerobić sobie kod modułu, który tutaj dołączyłem według swoich potrzeb. Oczywiście skorzystanie z tej metody, a nie z Hook'a globalnego pozbywasz się problemu z prawami Administratora na systemach z włączonym UAC i ewentualnym nie działaniem tak założonego Hooka na klawiaturę czy myszkę. Zarówno w dllce jak i poza nią.

//...
uses
  global_hotkeys;

procedure TestProc(KeyId : integer);
begin
  case KeyId of
    1 :
      begin
        MessageBox(Application.Handle, 'Boom!', 'Test', MB_OK);
      end;
  end;
end;

procedure TForm1.FormCreate(Sender : TObject);
begin
  RegisterGlobalHotKey(1, 0, Vk_RCONTROL, TestProc);
end;
0

Kod tego typu również używam w swoim programie do uruchamiania, zatrzymywania schematów oraz dodawania do niego instrukcji.

procedure TfrmMenu.FormCreate(Sender: TObject);
begin
      F1 := GlobalAddAtom('Hotkey1');
      F2 := GlobalAddAtom('Hotkey2');
      F3 := GlobalAddAtom('Hotkey3');
      F4 := GlobalAddAtom('Hotkey4');
      F5 := GlobalAddAtom('Hotkey5');
      F6 := GlobalAddAtom('Hotkey6');
      RegisterHotKey(Handle, F1, 0, VK_F1); // F1
      RegisterHotKey(Handle, F2, 0, VK_F2); // F2
      RegisterHotKey(Handle, F3, 0, VK_F3); // F3
      RegisterHotKey(Handle, F4, 0, VK_F4); // F4
      RegisterHotKey(Handle, F5, 0, VK_F5); // F5
      RegisterHotKey(Handle, F6, 0, VK_F6); // F6
end;

procedure TfrmMenu.FormDestroy(Sender: TObject);
begin
  UnRegisterHotKey(Handle, F1);
  UnRegisterHotKey(Handle, F2);
  UnRegisterHotKey(Handle, F3);
  UnRegisterHotKey(Handle, F4);
  UnRegisterHotKey(Handle, F5);
  UnRegisterHotKey(Handle, F6);
end;

Niestety nie mogę rejestrować tutaj ALT-a tylko musi on zostać przechwycony w hook-u, który niestety dla tego klawisza nie chce poprawnie działać.

Tak to wygląda w całości:

function KeyboardHookProc(nCode : Integer; wParam : integer; lParam : integer) : Integer; stdcall; export;
var
  Hook : PKBDLLHOOKSTRUCT;
  NewChar: array [0..1] of Char;
  s: String;
begin
  Hook := Pointer(lParam);
  case nCode of
    HC_ACTION :
      begin
        If not iMoveIt.getConfig.Hook_Paused Then
        begin
          FillChar(NewChar,2,#0);
          GetKeyboardState(KeyState);
          iMoveIt.GetDataFromCursor;

          case Hook.vkCode of
            VK_LSHIFT, VK_RSHIFT, VK_SHIFT:
              begin
                if FBuffer <> '' then
                begin
                  iMoveIt.AddAction(iMoveIt.getConfig.Action_TypeText, '', 0, 0, FBuffer + '1');
                  FBuffer := '';
                end;

                if (wParam = WM_KEYDOWN) then
                begin
                  if not SHIFT_WaitForUp then
                  begin
                    Start := GetTickCount;
                    iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, 0, '[SHIFT DOWN]');
                    SHIFT_WaitForUp := True;
                  end;
                end else
                begin
                  stop := GetTickCount;
                  iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, stop - start, '[SHIFT UP]');
                  SHIFT_WaitForUp := False;
                end;
              end;

            VK_ESCAPE:
              begin
                if FBuffer <> '' then
                begin
                  iMoveIt.AddAction(iMoveIt.getConfig.Action_TypeText, '', 0, 0, FBuffer + '1');
                  FBuffer := '';
                end;

                if (wParam = WM_KEYDOWN) then
                begin
                  if not ESC_WaitForUp then
                  begin
                    Start := GetTickCount;
                    iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, 0, '[ESC DOWN]');
                    ESC_WaitForUp := True;
                  end;
                end else
                begin
                  stop := GetTickCount;
                  iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, stop - start, '[ESC UP]');
                  ESC_WaitForUp := False;
                end;
              end;

            VK_RETURN:
              begin
                if FBuffer <> '' then
                begin
                  iMoveIt.AddAction(iMoveIt.getConfig.Action_TypeText, '', 0, 0, FBuffer + '1');
                  FBuffer := '';
                end;

                if (wParam = WM_KEYDOWN) then
                begin
                  if not ENTER_WaitForUp then
                  begin
                    Start := GetTickCount;
                    iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, 0, '[ENTER DOWN]');
                    ENTER_WaitForUp := True;
                  end;
                end else
                begin
                  stop := GetTickCount;
                  iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, stop - start, '[ENTER UP]');
                  ENTER_WaitForUp := False;
                end;
              end;

            VK_TAB:
              begin
                if FBuffer <> '' then
                begin
                  iMoveIt.AddAction(iMoveIt.getConfig.Action_TypeText, '', 0, 0, FBuffer + '1');
                  FBuffer := '';
                end;

                if (wParam = WM_KEYDOWN) then
                begin
                  if not TAB_WaitForUp then
                  begin
                    Start := GetTickCount;
                    iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, 0, '[TAB DOWN]');
                    TAB_WaitForUp := True;
                  end;
                end else
                begin
                  stop := GetTickCount;
                  iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, stop - start, '[TAB UP]');
                  TAB_WaitForUp := False;
                end;
              end;

            VK_LMENU, VK_RMENU, VK_MENU:
              begin
                if FBuffer <> '' then
                begin
                  iMoveIt.AddAction(iMoveIt.getConfig.Action_TypeText, '', 0, 0, FBuffer + '1');
                  FBuffer := '';
                end;

                if (wParam = WM_KEYDOWN) then
                begin
                  if not ALT_WaitForUp then
                  begin
                    Start := GetTickCount;
                    iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, 0, '[ALT DOWN]');
                    ALT_WaitForUp := True;
                  end;
                end else
                begin
                  stop := GetTickCount;
                  iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, stop - start, '[ALT UP]');
                  ALT_WaitForUp := False;
                end;
              end;

            VK_BACK:
              begin
                if FBuffer <> '' then
                begin
                  iMoveIt.AddAction(iMoveIt.getConfig.Action_TypeText, '', 0, 0, FBuffer + '1');
                  FBuffer := '';
                end;

                if (wParam = WM_KEYDOWN) then
                begin
                  if not BACKSPACE_WaitForUp then
                  begin
                    Start := GetTickCount;
                    iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, 0, '[BACKSPACE DOWN]');
                    BACKSPACE_WaitForUp := True;
                  end;
                end else
                begin
                  stop := GetTickCount;
                  iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, stop - start, '[BACKSPACE UP]');
                  BACKSPACE_WaitForUp := False;
                end;
              end;
          end;

          if (wParam = WM_KEYDOWN) then
          begin
            If ToAscii(Hook^.vkCode,Hook^.scanCode,KeyState,NewChar,0) = 1 Then
              FBuffer := FBuffer + NewChar[0];
          end;

        end;
      end;
  end;
  Result := CallNextHookEx(KeyboardHookHandle, nCode, wParam, lParam);
end;

dodanie znacznika <code class="delphi"> - fp

0

Udało mi się rozwiązać to w ten sposób:

Na początku ustawiam:

KeyDown := lParam and $80000000 = 0;

a tylko dla klawisza ALT sprawdzam tak:

              VK_LMENU, VK_RMENU, VK_MENU:
              begin
                if FBuffer <> '' then
                begin
                  iMoveIt.AddAction(iMoveIt.getConfig.Action_TypeText, '', 0, 0, FBuffer + '1');
                  FBuffer := '';
                end;

                if KeyDown{(wParam = WM_KEYDOWN)} then
                begin
                  if not ALT_WaitForUp then
                  begin
                    Start := GetTickCount;
                    iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, 0, '[ALT DOWN]');
                    ALT_WaitForUp := True;
                  end;
                end;
                if wParam = WM_KEYUP then
                begin
                  stop := GetTickCount;
                  iMoveIt.AddAction(iMoveIt.getConfig.Action_SpecialKey, '', 0, stop - start, '[ALT UP]');
                  ALT_WaitForUp := False;
                end;
              end;

:D dziwactwo, nie podoba mi się takie mieszanie w kodzie ale nic nie zrobię ;/
Ważne, że osiągnąłem zamierzony efekt.

Wielkie dzięki olesio za wszystkie sugestie i przydatne kody.

0

Spoko, cieszę się, ze mogłem pomóc. I ważne, że sobie poradziłeś. Jednak wydaje się mi, że porównujac Flags zamiast LParam uzyskał byś prawidłową informacje o trzymaniu klawisza nawet dla Altów.

1 użytkowników online, w tym zalogowanych: 0, gości: 1