NumPred: Globaler Hook mit Nachrichten nach außen

Im Moment entwickle ich ein kleines Programm, mit dem es später möglich sein soll, das NumPad als eine Tastatur zu verwenden, die einerseits das Schreiben mit einer Hand ermöglicht und andererseits durch den Einsatz von "Predictive Text" eine annehmbare Schreibgeschwindigkeit ermöglicht. Das Projekt selber habe ich NumPred getauft - als eine Kombination aus "NumPad" und "Predictive Text". 😀

NumPred wird aus zwei Teilen bestehen: Zum einen aus der NumPred.dll, die einen sogenannten Systemhook enthält und der NumPred.exe, die das eigentliche Programm darstellen wird.
Der Hook wird alle Tastatureingaben, die getätigt werden, analysieren, Tasteneingaben auf dem NumPad herausfiltern und an das Programm weiterleiten. Das Programm wird sich dann um die eigentliche Funktionalität kümmern: Sich merken, in welcher Reihenfolge die Tasten gedrückt wurden, Wortvorschläge heraussuchen und nach einer Bestätigung an das aktive Programm schicken.

Dass so etwas garnicht ausarten muss, möchte ich euch anhand des Hook-Quelltextes zeigen. Dieser ist - wie gesagt - für das filtern und weiterschicken der Tastatureingaben zuständig:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
library NumPred;

uses
  Windows,
  SysUtils,
  Messages,
  Classes;

var
  VHookHandle   : HHook   = 0;
  VWindowHandle : THandle = 0;

const
  CHookMessage = WM_APP + 4665;
  CHookWindow  = 'NumPredKeyboardHookWindow';

function KeyboardHook(ACode : Integer; wParam: WPARAM; lParam: LPARAM): LRESULT; stdcall;
const
  CPressedAfter  = 1 shl 31;
  CPressedBefore = 1 shl 30;
var
  LCallNext  : Boolean;
  LKeyStatus : Byte;
  LKBState   : TKeyboardState;
  LNumActive : Boolean;
begin
  LCallNext := true;
  if (ACode = HC_ACTION) then
  begin
    if (GetKeyboardState(LKBState)) then
    begin
      LNumActive := (LKBState[VK_NUMLOCK] = 1);
      if ((LNumActive) and (wParam in [VK_RETURN, VK_NUMPAD0..VK_DIVIDE])) then
      begin
        LKeyStatus := 0; // unknown key state
        if (((lParam and CPressedBefore) = 0) and ((lParam and CPressedAfter) = 0)) then
          LKeyStatus := 1; // key down state
        if (((lParam and CPressedBefore) <> 0) and ((lParam and CPressedAfter) <> 0)) then
          LKeyStatus := 2; // key up state

        if (LKeyStatus > 0) then
        begin
          if (VWindowHandle = 0) then
            VWindowHandle := FindWindow(nil, CHookWindow);

          if (VWindowHandle <> 0) then
            PostMessage(VWindowHandle, CHookMessage, wParam, LKeyStatus);
        end;

        // abort handling
        LCallNext := (VWindowHandle = 0);
      end;
    end;
  end;

  if LCallNext then
    Result := CallNextHookEx(VHookHandle, ACode, wParam, lParam)
  else
    Result := -1;
end;

function HookKeyboard : Boolean; stdcall;
begin
  Result := false;

  if (VHookHandle = 0) then
  begin
    VHookHandle := SetWindowsHookEx(WH_KEYBOARD, @KeyboardHook, hInstance, 0);

    Result := (VHookHandle <> 0);
    if (Result) then
      VWindowHandle := 0;
  end;
end;

function UnhookKeyboard : Boolean; stdcall;
begin
  Result := false;

  if (VHookHandle <> 0) then
  begin
    Result := UnhookWindowsHookEx(VHookHandle);
    if (Result) then
    begin
      VHookHandle   := 0;
      VWindowHandle := 0;
    end;
  end;
end;

exports
  HookKeyboard,
  UnhookKeyboard;

begin
end.

Der Hook unterscheidet zwischen dem drücken einer Taste und dem wieder loslassen der Taste. Die Informationen werden an das Fenster mit dem Titel "NumPredKeyboardHookWindow" gesendet - ich hoffe, dass dieser Name systemweit eindeutig genug ist. 😉

Die Anwendung, die die Informationen erhalten will, muss ebenfalls garnicht so viel machen - spannend ist dann wirklich erst die eigentliche Ersetzungslogik, die mich noch ein paar Stunden Arbeit kosten wird. So sieht der einfache Zugriff auf die Daten aus:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// [...]

const
  CHookMessage = WM_APP + 4665;
  CHookWindow  = 'NumPredKeyboardHookWindow';

// [...]

function HookKeyboard : Boolean; stdcall; external 'NumPred.dll';
function UnhookKeyboard : Boolean; stdcall; external 'NumPred.dll';

// [...]

procedure TMainForm.FormCreate(Sender : TObject);
begin
  InitializeWindow;
end;

procedure TMainForm.FormDestroy(Sender : TObject);
begin
  DeinitializeWindow;
end;

function TMainForm.HandleMessages(var AMessage: TMessage) : Boolean;
begin
  Result := false;

  if (AMessage.Msg = CHookMessage) then
  begin
    // AMessage.LParam enthält KeyDown/KeyUp Status
    // AMessage.LParam enthält KeyCode

    // abort handling
    Result := true;
  end;
end;

procedure TMainForm.DeinitializeWindow;
begin
  // deinitialize hook
  if not(UnhookKeyboard) then
    ShowMessage('Unhooking did not work out!');

  // disable message receiving
  Application.UnhookMainWindow(HandleMessages);
  Application.Title := '';
end;

procedure TMainForm.InitializeWindow;
begin
  // hide taskbar entry
  ShowWindow(Application.Handle, SW_HIDE);
  SetWindowLong(Application.Handle, GWL_EXSTYLE, GetWindowLong(Application.Handle, GWL_EXSTYLE) or WS_EX_TOOLWINDOW);
  ShowWindow(Application.Handle, SW_SHOW);

  // enable message receiving
  Application.Title := CHookWindow;
  Application.HookMainWindow(HandleMessages);

  // initialize hook
  if not(HookKeyboard) then
    ShowMessage('Hooking did not work out!');
end;

// [...]

initialization
  // only allow one instance
  if (FindWindow(nil, CHookWindow) <> 0) then
    Halt;

// [...]

Wünscht mir Glück, dass ich es schaffe, das Programm so schnell wie möglich fertig zu stellen. Ich habe schon einige Vorstellungen, wie der Lernprozess der Anwendung ablaufen soll und wie der eingegebene Text schlussendlich im aktiven Fenster (z.B. Word oder Firefox) landen soll. Aber die genaue Implementierung muss ich erst noch erarbeiten. 😉
Captain-Hook-Grüße, Kenny

3 Kommentare » Schreibe einen Kommentar

  1. Pingback: setwindowshookex

    • Man soll ja eine Frage nicht mit einer Gegenfrage beantworten aber: Was meinst du genau? Geht es dir um den technischen Aspekt oder fragst du dich, weshalb man am PC T9-like schreiben können sollte?

      Zum technischen Aspekt:
      Es ist eine saubere kleine Lösung, die den Großteil der Arbeit aus dem System auslagert. Dadurch erhält man einen geringeren Overhead und mehr Möglichkeiten, als wenn man alles direkt im Hook implementieren würde.

      Zum generellen Aspekt:
      Für mich ist es die Bequemlichkeit. Ich finde den Gedanken angenehm, einfach ein NumPad per USB ans Notebook anzuschließen und dann einhändig schreiben zu können. Man könnte z.B. darüber nachdenken, auch die Maussteuerung über einen Programmmodus mit abzubilden. Dann könnte man mit einer Hand sowohl den Mauszeiger steuern, als auch Tastatureingaben tätigen.

Schreibe einen Kommentar

Um Ihnen beim weiteren Kommentieren auf dieser Webseite die erneute Eingabe Ihrer Daten zu ersparen, wird beim Absenden Ihres Kommentars ein Cookie an Ihren Browser gesendet und von diesem gespeichert. Mit dem Absenden eines Kommentars auf dieser Webseite stimmen Sie der Speicherung und Übertragung dieses Cookies explizit zu.

Pflichtfelder sind mit * markiert.