Writing a keyboard driver

Writing a keyboard driver means that hooks must be created for most of the keyboard unit functions. The TKeyBoardDriver record contains a field for each of the possible hooks:

TKeyboardDriver = Record
  InitDriver : Procedure;
  DoneDriver : Procedure;
  GetKeyEvent : Function : TKeyEvent;
  PollKeyEvent : Function : TKeyEvent;
  GetShiftState : Function : Byte;
  TranslateKeyEvent : Function (KeyEvent: TKeyEvent): TKeyEvent;
  TranslateKeyEventUniCode: Function (KeyEvent: TKeyEvent): TKeyEvent;
end;

The meaning of these hooks is explained below:

InitDriver
Called to initialize and enable the driver. Guaranteed to be called only once. This should initialize all needed things for the driver.
DoneDriver
Called to disable and clean up the driver. Guaranteed to be called after a call to initDriver. This should clean up all things initialized by InitDriver.
GetKeyEvent
Called by GetKeyEvent . Must wait for and return the next key event. It should NOT store keys.
PollKeyEvent
Called by PollKeyEvent . It must return the next key event if there is one. Should not store keys.
GetShiftState
Called by PollShiftStateEvent . Must return the current shift state.
TranslateKeyEvent
Should translate a raw key event to a correct key event, i.e. should fill in the shiftstate and convert function key scancodes to function key keycodes. If the TranslateKeyEvent is not filled in, a default translation function will be called which converts the known scancodes from the tables in the previous section to a correct keyevent.
TranslateKeyEventUniCode
Should translate a key event to a UNICode key representation.

Strictly speaking, only the GetKeyEvent and PollKeyEvent hooks must be implemented for the driver to function correctly.

The example unit demonstrates how a keyboard driver can be installed. It takes the installed driver, and hooks into the GetKeyEvent function to register and log the key events in a file. This driver can work on top of any other driver, as long as it is inserted in the uses clause after the real driver unit, and the real driver unit should set the driver record in its initialization section.

Note that with a simple extension of this unit could be used to make a driver that is capable of recording and storing a set of keyboard strokes, and replaying them at a later time, so a 'keyboard macro' capable driver. This driver could sit on top of any other driver.

Example

unit logkeys;
interface
Procedure StartKeyLogging;
Procedure StopKeyLogging;
Function  IsKeyLogging : Boolean;
Procedure  SetKeyLogFileName(FileName : String);
implementation
uses sysutils,keyboard;
var
  NewKeyBoardDriver,
  OldKeyBoardDriver : TKeyboardDriver;
  Active,Logging : Boolean;
  LogFileName : String;
  KeyLog : Text;
Function TimeStamp : String;
begin
  TimeStamp:=FormatDateTime('hh:nn:ss',Time());
end;
Procedure StartKeyLogging;
begin
  Logging:=True;
  Writeln(KeyLog,'Start logging keystrokes at: ',TimeStamp);
end;
Procedure StopKeyLogging;
begin
  Writeln(KeyLog,'Stop logging keystrokes at: ',TimeStamp);
  Logging:=False;
end;
Function IsKeyLogging : Boolean;
begin
  IsKeyLogging:=Logging;
end;
Function LogGetKeyEvent : TKeyEvent;
Var
  K : TKeyEvent;
begin
  K:=OldkeyboardDriver.GetKeyEvent();
  If Logging then
    begin
    Write(KeyLog,TimeStamp,': Key event: ');
    Writeln(KeyLog,KeyEventToString(TranslateKeyEvent(K)));
    end;
  LogGetKeyEvent:=K;
end;
Procedure LogInitKeyBoard;
begin
  OldKeyBoardDriver.InitDriver();
  Assign(KeyLog,logFileName);
  Rewrite(KeyLog);
  Active:=True;
  StartKeyLogging;
end;
Procedure LogDoneKeyBoard;
begin
  StopKeyLogging;
  Close(KeyLog);
  Active:=False;
  OldKeyBoardDriver.DoneDriver();
end;
Procedure SetKeyLogFileName(FileName : String);
begin
  If Not Active then
    LogFileName:=FileName;
end;
Initialization
  GetKeyBoardDriver(OldKeyBoardDriver);
  NewKeyBoardDriver:=OldKeyBoardDriver;
  NewKeyBoardDriver.GetKeyEvent:=@LogGetKeyEvent;
  NewKeyBoardDriver.InitDriver:=@LogInitKeyboard;
  NewKeyBoardDriver.DoneDriver:=@LogDoneKeyboard;
  LogFileName:='keyboard.log';
  Logging:=False;
  SetKeyboardDriver(NewKeyBoardDriver);
end.  

Example

program example9;
{ This program demonstrates the logkeys unit }
uses keyboard,logkeys;
Var
  K : TKeyEvent;
begin
  InitKeyBoard;
  Writeln('Press keys, press "q" to end, "s" toggles logging.');
  Repeat
    K:=GetKeyEvent;
    K:=TranslateKeyEvent(K);
    Writeln('Got key : ',KeyEventToString(K));
    if GetKeyEventChar(K)='s' then
      if IsKeyLogging then
        StopKeyLogging
      else
        StartKeyLogging;
  Until (GetKeyEventChar(K)='q');
  DoneKeyBoard;
end.