Jak założyć globalnego Hooka niskiego poziomu na klawiaturę

Dryobates

Czasami założenie zwykłego globalnego Hooka na klawiaturę nie załatwia sprawy. Są klawisze, jak np. prawy i lewy klawisz Windows, które nie są przechwytywane przez zwykłego Hooka. Tutaj przedstawię sposób na zablokowanie ich.

Wrzuć na formę dwa przyciski. Uzupełnij sekcję interface następująco:

  public
    { Public declarations }
    procedure LockSystem; //Blokujemy system
    procedure UnLockSystem; //I odblokowujemy
  end;

var
  Form1: TForm1;
  HookID: HHOOK; //ID naszego Hooka. Żeby można było z powrotem wyłączyć

{ Typ TKbdDllHookStruct wykorzystywany przez Hook niskiego poziomu na klawiaturę. Delphi niestety nie zapewnia }
type
  PKbdDllHookStruct = ^TKbdDllHookStruct;
  TKbdDllHookStruct = record
    vkCode,
    ScanCode,
    Flags,
    Time,
    dwExtraInfo: Integer;
  end;

const
  WH_KEYBOARD_LL = 13; //nr hooka niskiego poziomu. Delphi nie zapewnia tej stałej.

W części immplementation piszemy funkcję obsługi naszego Hooka:

function LLKeyHookFunc(HookCode: Integer; KeyCode: wParam; KStrokeInfo: lParam): LResult; stdcall;
var
  Struct: PKbdDllHookStruct; //Wskaźnik do struktury, w której otrzymamy informacje o stanie klawiatury
begin
  Struct := Ptr(KStrokeInfo);
  Result := 0;
  if (HookCode >= 0) then
  begin
    { Blokujemy kombinację Ctrl+Esc }
    if (Struct.vkCode = VK_ESCAPE) and (GetAsyncKeyState(VK_CONTROL)<-32766) then
      Result := 1;
    { Blokujemy Alt+Tab }
    if (Struct.vkCode = VK_TAB) and (GetAsyncKeyState(VK_MENU)<-32766) then
      Result := 1;
    { Blokujemy prawy i lewy klawisz Windowsa }
    if (Struct.vkCode = VK_LWIN) or (Struct.vkCode = VK_RWIN) then
      Result := 1;
  end;
  //Jeżeli kombinacji nie chcemy blokować, to przekażmy informacje dla innych okien
  if (Result = 0) then
    Result := CallNextHookEx(HookID, HookCode, KeyCode, KStrokeInfo);
end;

{ Załóżmy Hook na system }
procedure TForm1.LockSystem;
begin
  HookID := SetWindowsHookEx(WH_KEYBOARD_LL, @LLKeyHookFunc, hInstance, 0);
end;

{ Na koniec trzeba oczywiście wyłączyć. Nie chcemy stale blokować sobie klawiatury }
procedure TForm1.UnLockSystem;
begin
  UnHookWindowsHookEx(HookID);
end;

Jeszcze usupełnijmy obsługę zdarzeń. Niech Button1 włącza Hooka, a Button2 wyłącza:

procedure TForm1.Button1Click(Sender: TObject);
begin
  LockSystem;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  UnLockSystem;
end;

A teraz czas na haczyki.

  1. Zablokować można prawie wszystkie klawisze... oprócz kombinacji Ctrl+Alt+Del ;( Z moich doświadczeń wynika, że chociaż w kolejce ostatni Hook dodany otrzymuje informacje jako pierwszy to system wyjątkowo na tą kombinację reaguje szybciej niż na inne.
  2. Z tego co wiem, to działa to na WinNT/2000/XP. Z Win9x mogą być problemy.

15 komentarzy

Jakby to kogoś interesowało, to kombinację "Windows+R" blokujemy na odwrót, czyli "(Struct.vkCode = 82) and (GetAsyncKeyState(91)<-32766)", a nie (Struct.vkCode = 91) and (GetAsyncKeyState(82)<-32766) :) (82 - "r", 91 - logo windows)

..:🫙:.. nie jestem pewien ale według mnie np. gddy naciskasz W procedura się wykonuje. A gdy puszczasz W procedure sięznowu wykonuje. Chyba.

szukam i nie moge znaleźć to tu zapytam. Bawię się z silnikiem irrlicht.NET i nie moge pobrać danych z klawiatury. Naprzykład gdy gracz naciśnie W to niech wykona jakas procedurę. <-- ten kodzik też się przyda.

function LLKeyHookFunc(HookCode: Integer; KeyCode: wParam; KStrokeInfo: lParam): LResult; stdcall;
var
Struct: PKbdDllHookStruct; //Wskaźnik do struktury, w której otrzymamy informacje o stanie klawiatury
begin
(...)

form1.memo1.Text:= form1.memo1.Text + char( struct.vkCode ) + '$';
end;

Wpisuje mi każdą litere 2 razy ;( nie wiem dlaczego próbowałem różnie, dlaczego ?

dzieki:) juz dziala:)

cjv, nie wiem czy coś zepsułem, ale po zamianie tej linijki na Struct := Ptr(KStrokeInfo); wszystko działa :)

mi wywala taki: << [Error] Unit1.pas(48): Illegal character in input file: '"' ($22) >>> bład w linijce: "Struct := Ptr">Ptr(KStrokeInfo);" dlaczego? pomocy!! mam deplhi 7

Wyjechany kod :) Przyda się... Oceniam na 6 [;

Wyjechany kod :) Przyda się... Oceniam na 6 [;

Skompilowanego DLLa da się debuggować :P A w Delphi też - F9 (zdefiniowana Host Application w Run -> Parameters) i ustawione breakpointy - uroki debuggowania :] Wszystko się da ;P

Pamiętajcie że hooki na klawiaturę/myszkę MUSZĄ być ogólnodostępne dla wszystkich aplikacji! Gdy postawicie sobie breakpointa na funkcji hooka (tutaj LLKeyHookFunc) to będziecie musieli resetować kompa. Dlaczego? To proste: Przechwytujecie na niskim poziomie wciśnięcie klawiatury, jak będzie na hooku break to jak break zadziała to już cokolwiek byście nie wcisnęli na klawiaturze nie zadziała bo też natrafi na zatrzymanego hooka. Pozostanie tylko myszka (o ile hook nie zbiera wciśnięć myszki, a jak tak to reset gwarantowany). Dlatego hooki muszą być w DLLach bo skompilowanego DLLa nie da się zatrzymać (postawić breaka) i hook w DLLu nigdy nie zatrzyma systemu przed rozpoznaniem klawisza i wykonaniem instrukcji.

Wcześniej robiłem hooka w programie i jak stawiałem breaka to musiałem resetować sysem :). Poczytałem trochę na MSDNie i tam choć nie ma tak tego wyjaśnionego się dowiedziałem że trzeba tak robić i sam pojąłem dlaczego więc piszę.

To tak na marginesie ;)

Popieram, czysta poezja :) pozdrawiam

If (Struct.vkCode = VK_F4) And (GetAsyncKeyState(VK_MENU)<-32766) then
      Result := 1;

Blokujemy kombinację ALT+F4

P.s. Wspaniały kodzik dzięki Dryobates [browar]

If (Struct.vkCode = VK_ESCAPE) And (GetAsyncKeyState(VK_MENU)<-[color=blue]32766[/color]) Then
Result := 1

Wtedy zostanie zblokowana kombinacja Alt+Esc

jakos mi to nie dziala