Translate Key Code (message.lparam)

With a keyHook I receive messages.
With:
    GetKeyNameText(Message.LParam, @Character, 100);
I receive the Name of the key from the message. e.g. J, L, SHIFT, ENTER, F1

So far so good, but the problem is, I can display only the "first set" of chars, not considering the status of the shift key. (e.g. I press :, but GetKeyNameText returns first "SHIFT" and then ";"

At another place I receive and store the status of the shift key. With SHIFT and message.lparam I should be able to get the real key, the user pressed!?

So I tried this
 .. which should normaly produce the same text like the memo which the user have entered originally.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
AddKey(Message.WParam,STRING(CHARACTER));
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Procedure TSpyKeyForm.AddKey(key:word;STR:STRING);
VAR ch:STRING;
CONST
    AddAscii: integer = 32;
begin
        IF shift THEN AddAscii := 0 ELSE AddAscii := 32;
        case KEY of
        16: SHIFT:= true; //set shift private var,
        13: S:= S + #10#13;
        32: begin ch:= ' '; S:= S + CH ; end;
        186 .. 230: s:=s+STR;
        65 .. 90: s:=s+char(KEY + addAscii);
        else
        s:=s+' ['+STR+'] ';//char(KEY + addAscii);
        END;
end;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This procedure is far away from being finished, but I think there must be a API funktion to perform this kind of translation?!

Question:
Maybe a kind of GetKeyNameText which also take the shift key in order to give me the real character the user pressed, or a function like the one above using the message or ascii code and the SHIFT status.

.................................................
Here the unit:

UNIT KeyHook_DLL_U2;

INTERFACE

USES
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
    StdCtrls;

CONST
    My_Hook_DLL = 'KHD.dll';
    MY_HOOK_MSSG = WM_USER + $1000;

TYPE
    THookTeclado = PROCEDURE; stdcall;

TYPE
    TSpyKeyForm = CLASS(TForm)
        Memo1: TMemo;
        Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
        PROCEDURE FormCreate(Sender: TObject);
        PROCEDURE FormDestroy(Sender: TObject);
        PROCEDURE FormClick(Sender: TObject);
    Private
        { Private declarations }
        shift: boolean;
        File_in_Mem: THandle;
        PReceiver: ^Integer;
        HandleDLL: THandle;
        HookOn,
            HookOff: THookTeclado;
        s: STRING;
        PROCEDURE Sys_Hook(VAR message: TMessage); Message MY_HOOK_MSSG;
    procedure AddKey(key: word;STR:STRING);
    Public
        { Public declarations }
    END;

VAR
    SpyKeyForm: TSpyKeyForm;

IMPLEMENTATION

{$R *.DFM}

PROCEDURE TSpyKeyForm.FormCreate(Sender: TObject);
BEGIN

    {We dont want that the memo read the keyboard...}
    Memo1.ReadOnly := TRUE;
    //Find DLL
    HandleDLL := LoadLibrary(PChar(ExtractFilePath(Application.Exename) + My_Hook_DLL));
    IF HandleDLL = 0 THEN
        RAISE Exception.Create('DLL not found');
    //get address of dll functions
    @HookOn := GetProcAddress(HandleDLL, 'HookOn');
    @HookOff := GetProcAddress(HandleDLL, 'HookOff');

    IF NOT assigned(HookOn) OR
        NOT assigned(HookOff) THEN
        RAISE Exception.Create('Cannot find the required DLL functions');

    File_in_Mem := CreateFileMapping($FFFFFFFF,
        NIL,
        PAGE_READWRITE,
        0,
        SizeOf(Integer),
        'MyMappedFile'); //in dll

    IF File_in_Mem = 0 THEN
        RAISE Exception.Create('Error while create file');

    PReceiver := MapViewOfFile(File_in_Mem, FILE_MAP_WRITE, 0, 0, 0);

    PReceiver^ := Handle;
    HookOn;
END;

PROCEDURE TSpyKeyForm.Sys_Hook(VAR message: TMessage);
VAR
    Character: ARRAY[0..100] OF char;
    Key_Action: STRING; ch:string;
    ScanCode:word;
BEGIN
    {Virtual key code to Key Name}
    GetKeyNameText(Message.LParam, @Character, 100);
    ch:=string(character);
    //message from dll
   
    ScanCode:= MapVirtualKey(Message.wParam,0);
    label2.caption:=inttostr(scancode);
    label3.caption:=char(scancode);
    {Look if the key was pressed, released o re-pressed}
    IF ((Message.lParam SHR 31) AND 1) = 1 THEN
        BEGIN
            Key_Action := 'Released'; {Released}
            IF message.wparam = 16 THEN shift := false;
        END
    ELSE
        IF ((Message.lParam SHR 30) AND 1) = 1 THEN Key_Action := 'Repressed' {repressed}
    ELSE
        BEGIN Key_Action := 'pressed'; {pressed}
          AddKey(Message.WParam,STRING(CHARACTER));

       // s := s + ch;//STRING(Character);
        //        end;
        END;
    //if message.wparam = 16 then shift:= true else shift := false;

    //IF shift THEN showmessage('shift');
END;

Procedure TSpyKeyForm.AddKey(key:word;STR:STRING);
VAR ch:STRING;
CONST
    AddAscii: integer = 32;
begin
        IF shift THEN AddAscii := 0 ELSE AddAscii := 32;
        case KEY of
        16: shift := true;
        13: S:= S + #10#13;
        32: begin ch:= ' '; S:= S + CH ; end;
        186 .. 230: s:=s+STR;
        65 .. 90: s:=s+char(KEY + addAscii);
        else
        s:=s+' ['+STR+'] ';//char(KEY + addAscii);
        END;


    label1.caption :=
        inttostr(KEY) + '  '
        + STR + '  '
        + char(KEY + addAscii);


end;

PROCEDURE TSpyKeyForm.FormDestroy(Sender: TObject);
BEGIN
    {Uninstall the Hook}
    IF Assigned(HookOff) THEN
        HookOff;

    {Free the DLL}
    IF HandleDLL <> 0 THEN
        FreeLibrary(HandleDLL);

    {Close the memfile and the View}
    IF File_in_Mem <> 0 THEN
        BEGIN
            UnmapViewOfFile(PReceiver);
            CloseHandle(File_in_Mem);
        END;

END;

PROCEDURE TSpyKeyForm.FormClick(Sender: TObject);
var sl:TStringlist;
BEGIN
sl:=tstringlist.create;
    //memo1.text:=s;
    //memo1.text:=s;// := s;
    //showmessage(s);
    sl.text:=s;
    sl.SaveToFile(extractfilepath(application.exename)+'test1.txt');
    memo1.Lines.LoadFromFile('test1.txt');
    //memo1.Lines.SaveToFile('test2.txt');
END;

END.
LVL 1
hush021299Asked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Slick812Commented:
hello hush, I remember trying to get the Char out of the KeyHook, and then trying to figure out how to use ToAscii( ) the following code might save you alot of time.


var
CharString: String;
{CharString will have all the ASCII charaters typed, keys like
the Ctrl and Alt and F1 will not be added}

function KeyHookFunc(Code, VirtualKey{wParam}, KeyStroke{lParam}: Integer): LRESULT; stdcall;
var
KeyState1: TKeyBoardState;
AryChar: Array[0..1] of Char;
Count: Integer;

begin
Result := 0;
if Code = HC_NOREMOVE then Exit;
Result := CallNextHookEx(hKeyHook, Code, VirtualKey, KeyStroke);
if Code < 0 then
Exit;
if Code = HC_ACTION then
  begin
  if ((KeyStroke and (1 shl 30)) = 0) then
    begin
    GetKeyboardState(KeyState1);
    Count := ToAscii(VirtualKey,KeyStroke, KeyState1, AryChar, 0);
    if Count = 1 then
    CharString := CharString+ AryChar[0];
    //SendMessage(hMemo,WM_CHAR, Ord(AryChar[0]), 0);
    end;
  end;

end;


- - - - - - - - - - - - - - - - - - - -

hope this helps you, ask questions if you need more.
robert_marquardtCommented:
Your problem is one of understanding. GetKeyNameText returns the name of the "physical" key. There "Shift" is just a normal key.

BTW GetKeyNameText is unreliable. It is implemented in a language specific DLL. The Windows 2000 version of the DLL is inferior to the Windows 98 version.
hush021299Author Commented:
Hello Slick812

Thanks a lot. I think this is the right clue, but I do not understand completely this part:

IF ((Message.lParam AND (1 SHL 30)) = 0) THEN
                    BEGIN ...

Does this show a keypress?
                         
Microsoft Azure 2017

Azure has a changed a lot since it was originally introduce by adding new services and features. Do you know everything you need to about Azure? This course will teach you about the Azure App Service, monitoring and application insights, DevOps, and Team Services.

Slick812Commented:
I should have explained that, the First Bit in the KeyStroke (lparam) will be on if the key is down and off if the key is up so you test to see if it's down and do the charater, otherwise you'll get 2 Chars (one for key down and one for Key up) and you get double charaters, That one was hard to figure.
hush021299Author Commented:
I hope I dont confuse u know.
The first code above is based on my first attempt using a dll. No I am at another place and try to understand your example code.
I just found a code (DLL, HookComponent and Unit) which I found on EExch. This code works fine so far. But then I tried to enter a part of your snippet in the keyHook eventhandler:

procedure TForm1.HookLib1KeyEvent(Sender: TObject; wParam,
  lParam: Integer);
VAR
     KeyState1: TKeyBoardState;
     AryChar: ARRAY[0..1] OF Char;
     Count: Integer;
BEGIN
     IF ((lParam AND (1 SHL 30)) = 0) THEN
     BEGIN
          GetKeyboardState(KeyState1);
          Count := ToAscii(wParam, lParam, KeyState1, AnyChar, 0);
          IF Count = 1 THEN
               CharString := CharString + AryChar[0];
   //SendMessage(hMemo,WM_CHAR, Ord(AryChar[0]), 0);
     END;
    memo2.text:=charstring;
   Inc(KeyboardCount);
   Label2.Caption:=Format('%d',[KeyboardCount]);
end;

The result is: It works fine as long I focus the memo, but when I key my text in another object,
I get the ascii-keystrokes,
but again only the non capital and first row of the keyboard.
(Test in internal Memo
this is when writing into notepad ,.-,.-)<=  

Why is that so?

hush021299Author Commented:
...forgot something
basically I seems to get any keys, e.g. the shift key (16) and then the 1 (49) key. The system translates toASCII.
So when I figure out that a shift key is pressed then a "1" becomes a "!"

So I know that 1 = 16, but when I press ! it becomes to something else (I can not simply add 32). This is the part I do not understand.

Now the toASCII or getKeyNameText functions dont take shift as a parameter.

How can I know figure out what key really is shown in the originating document?
Slick812Commented:
??, I read your comments and looked at your code, and I don't get what you have problems. I guess you are not getting the correct Charaters because of what I call "Out of Process" keboard state wierdness, but thats just a guess. I don't want to take the time to run the code you have shown, as far as I can tell none of it is in a Library (.DLL). What I'll do is give you some code that works for me.

1. Library Code for a Key Hook DLL
- - -

library HookKey1;

uses
  Windows,
  Messages,
  SysUtils;

type
  PHookRec = ^THookRec;
  THookRec = Record
    AppHnd: Integer;
    MemoHnd: Integer;
    end;

var
Hooked: Boolean;
hKeyHook, hMemo, hMemFile, hApp: HWND;
PHookRec1: PHookRec;

function KeyHookFunc(Code, VirtualKey, KeyStroke: Integer): LRESULT; stdcall;
var
KeyState1: TKeyBoardState;
AryChar: Array[0..1] of Char;
Count: Integer;
begin
Result := 0;
if Code = HC_NOREMOVE then Exit;
Result := CallNextHookEx(hKeyHook, Code, VirtualKey, KeyStroke);
{I moved the CallNextHookEx up here but if you want to block
or change any keys then move it back down}
if Code < 0 then
Exit;

if Code = HC_ACTION then
  begin
  if ((KeyStroke and (1 shl 30)) <> 0) then
  if not IsWindow(hMemo) then
  begin
{I moved the OpenFileMapping up here so it would not be opened
unless the app the DLL is attatched to gets some Key messages}
  hMemFile := OpenFileMapping(FILE_MAP_WRITE, False, 'Global7v9k');
  PHookRec1 := MapViewOfFile(hMemFile, FILE_MAP_WRITE, 0, 0, 0);
    if PHookRec1 <> nil then
      begin
      hMemo := PHookRec1.MemoHnd;
      hApp := PHookRec1.AppHnd;
      end;
  end;
  if ((KeyStroke and (1 shl 30)) <> 0) then
    begin
    GetKeyboardState(KeyState1);
    Count := ToAscii(VirtualKey,KeyStroke, KeyState1, AryChar, 0);
    if Count = 1 then
      begin
      SendMessage(hMemo,WM_CHAR, Ord(AryChar[0]), 0);
{I included 2 ways to get the Charaters, a Memo Hnadle and
a WM_USER+1678 message to the program}
      PostMessage(hApp, WM_USER+1678, Ord(AryChar[0]), 0);
      end;
    end;
  end;

end;


function StartHook(MemoHandle, AppHandle: HWND) : Byte; export;
begin
Result := 0;
if Hooked then
  begin
  Result := 1;
  Exit;
  end;
if not IsWindow(MemoHandle) then
  begin
  Result := 4;
  Exit;
  end;
hKeyHook := SetWindowsHookEx(WH_KEYBOARD, KeyHookFunc, hInstance, 0);
if hKeyHook > 0 then
  begin
{you need to use a mapped file because this DLL attatches to every app
that gets windows messages when it's hooked, and you can't get info except
through a Globally avaiable Mapped file}
  hMemFile := CreateFileMapping($FFFFFFFF, // $FFFFFFFF gets a page memory file
                nil,                // no security attributes
                PAGE_READWRITE,      // read/write access
                0,                   // size: high 32-bits
                SizeOf(THookRec),           // size: low 32-bits
                //SizeOf(Integer),
                'Global7v9k');    // name of map object
  PHookRec1 := MapViewOfFile(hMemFile, FILE_MAP_WRITE, 0, 0, 0);
  hMemo := MemoHandle;
  PHookRec1.MemoHnd := MemoHandle;
  hApp := AppHandle;
  PHookRec1.AppHnd := AppHandle;
{set the Memo and App handles to the mapped file}
  Hooked := True;
  end else
  Result := 2;
end;

function StopHook: Boolean; export;
begin
if PHookRec1 <> nil then
  begin
  UnmapViewOfFile(PHookRec1);
  CloseHandle(hMemFile);
  PHookRec1 := Nil;
  end;
if Hooked then
Result := UnhookWindowsHookEx(hKeyHook) else
Result := True;
Hooked := False;
end;

procedure EntryProc(dwReason : DWORD);
begin
 if (dwReason = Dll_Process_Detach) then
 begin
 if PHookRec1 <> nil then
  begin
  UnmapViewOfFile(PHookRec1);
  CloseHandle(hMemFile);
  end;
 UnhookWindowsHookEx(hKeyHook);
 end;
end;

exports
  StartHook,
  StopHook;

begin
PHookRec1 := nil;
Hooked := False;
hKeyHook := 0;
hMemo := 0;
DLLProc := @EntryProc;
EntryProc(Dll_Process_Attach);
end.


++++++++++++++++++++++++++++++++++++++++++++++++++++++++

2. Code from the calling Program
{this program get's the Char from the DLL in 2 ways,
as a Char message to a Memo and as a DLLMessage WM_USER+1678}
---


  private
    { Private declarations }
    hLib: THandle;
    DllStr1: String;
    procedure DllMessage(var Msg : TMessage); message WM_USER+1678;


procedure TForm1.DllMessage(var Msg : TMessage);
begin
if (Msg.WParam = 8) or (Msg.WParam = 13) then Exit;
{the 8 is the Backspace and the 13 if the Enter key, You'll need to
do some special handleing for a string}
DllStr1 := DllStr1+ Chr(Msg.WParam);
Label1.Caption := DllStr1;
end;

procedure TForm1.but_StartHookClick(Sender: TObject);
type
TStartHook = function(MemoHandle, AppHandle: HWND): Byte;
var
StartHook1: TStartHook;
SHresult: Byte;
begin
hLib2:=LoadLibrary('HookKey1.dll');
@StartHook1 := GetProcAddress(hLib2, 'StartHook');
if @StartHook1 = nil then Exit;
SHresult := StartHook1(Memo1.Handle, Handle);
if SHresult = 0 then ShowMessage('the Key Hook was Started, good');
if SHresult = 1 then ShowMessage('the Key Hook was already Started');
if SHresult = 2 then ShowMessage('the Key Hook can NOT be Started, bad');
if SHresult = 4 then ShowMessage('MemoHandle is incorrect');
end;

procedure TForm1.but_StopHookClick(Sender: TObject);
type
TStopHook = function: Boolean;
var
StopHook1: TStopHook;
hLib21: THandle;
begin
@StopHook1 := GetProcAddress(hLib2, 'StopHook');
if @StopHook1 = nil then
  begin
  ShowMessage('Stop Hook DLL Mem Addy not found');
  Exit;
  end;
if StopHook1 then
ShowMessage('Hook was stoped');
FreeLibrary(hLib2);
{for some reason in Win XP you need to call FreeLibrary twice
maybe because you get 2 functions fron the DLL? ?}
FreeLibrary(hLib2);
end;


 == = = = =  = = = = = = = =========================================
Look this over and ask about stuff you need more info for.
about the question "Now the toASCII or getKeyNameText functions dont take shift as a parameter"
ToAscii does get the state of the shift key, what do you think the "GetKeyboardState" does? It also gets the Ctrl and ALT key state and blocks the Char if pressed. . .
your question "So I know that 1 = 16, but when I press ! it becomes to something else (I can not simply add 32)" Yes you got it, if you could just add 32 then it would be so easy, but it don't work with punctuation, ,  like the difference between the . and the > is 16, and the others are all different.

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
tong111797Commented:
I got the same exactly problem with HUSH.
That spanish code has something wrong with SHIFT+key.
But it's look like a good code.



lodibabuCommented:
Alright...

When ever someone in my computer presses a key stroke ..is your DLL will work.

BM
fatihbarutCommented:
good job!
however you should correct the hlib2 and hlib mistake.
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Delphi

From novice to tech pro — start learning today.