Solved

Listen for keyboard (barcode reader) input in delphi application, do something if certain input detected.

Posted on 2009-07-09
19
3,545 Views
Last Modified: 2012-05-07
Hi experts

I am using a USB Barcode Scanner to scan in 2D barcodes (2D barcodes contain several hundred characters of usually ASCII text).
The problem is that the Barcode Scanner inputs the same as a keyboard. If I open Notepad and scan a barcode it will type everything into Notepad.
So, in my application, I need to detect the barcode scanner's input, probably by listening for all keyboard events.
Normally it is impossible to detect which input is from the keyboard and which input is from the Barcode Scanner.
But my plan is to put a CODEWORD into the barcode, something like "Hello World".
If the keyboard listener detects C + O + D + E + W + O + R + D it will know that this is a Barcode being scanned and to input it into a specific TMemo on a specific Form.

Thanks :)
0
Comment
Question by:rfwoolf
19 Comments
 
LVL 6

Expert Comment

by:FactorB
ID: 24811560
Maybe sounds too simple, but why don't you check input string

var
inpstr,cdwrd:string;
begin
inpstr:=Edit1.Text;
cdwrd:='codeword';
if leftstr(inpstr,length(cdwrd))=cdwrd then call_some_function ;
end;

Regards,
B.
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24811571
Thanks for the idea FactorB,
But I would then have to go to every single input in my application and attach this procedure. I would rather have a listener running on every form that detects keyboard input, but this I do not know how to do.
0
 
LVL 6

Expert Comment

by:bokist
ID: 24811622
Try  OnKeyDown event on edited field:

procedure TForm1.edit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if  pos('CODEWORD', trim(edit1.Text)) > 0  then  -- >  barcode scanned
end;
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24811650
Hi bokist. I think your idea is very similar to FactorB. I would have to go to every input field in my application and put this function.
Then what about controls that cannot receive keyboard input, like TImage?
You see the problem?

There are some solutions to this but I'm having some trouble implementing them:

[Solution 1]:
Intercepting Keyboard Input with Delphi - Implementing a Keyboard Hook
http://delphi.about.com/od/windowsshellapi/a/keyboard_hook.htm

[Solution 2]:
"Barcode reader"
http://www.experts-exchange.com/Programming/Languages/Pascal/Q_24022641.html
I don't know how to use this procedure:
procedure ApplicationMessage(var Msg: TMsg;var Handled: Boolean);
begin
   case Msg.Message of
    WM_CHAR:
    begin
      if ToAsciiEx(Msg.wParam,MapVirtualKeyEx(Msg.wParam,2,1),KeyState,@LastKey,0,1)>0 then
      begin
         Key:=Ord(LastKey);
         BarcodeString := BarcodeString + IntToAscii(Key);
      end;
    end;
    WM_KEYDOWN, WM_KEYUP:
    case Msg.wParam of
    VK_RETURN:
      begin
       if BarcodeString <> '' then
       begin
           Edit2.SetFocus;
           Edit2.Text := BarcodeString;
           BarcodeString := '';
       end;
      end;
     end;
    end;
end;
0
 
LVL 13

Expert Comment

by:ThievingSix
ID: 24811662
-Hmm, my suggestion should be a system hook with SetWindowsHookEx() with the WH_KEYBOARD_LL.
-When the hook starts you would create a char array the size of your secret word(or just a string I suppose).
-Every time a new character is caught you would add it to the buffer. If the buffer hits the code words size you check it, if its the real deal you start logging and you can even tell windows to ignore them.
-You would have to have a end secret code to know when to stop unless the strings are normal length. I guess you could do the same with the buffer idea to find out if that comes.
0
 
LVL 13

Expert Comment

by:ThievingSix
ID: 24811668
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24811696
Hi ThievingSix,
Your first post is right on the money except I'm not used to working with hooks and I'm worried I'll make some mistakes. For example how do I make this hook application-wide? or do I have to set this hook up on the OnCreate event of every form? (maybe that's not a bad idea since some forms must ignore barcode input, while some forms need to extract different information from the barcode than others).

If this is easy for you, could you create for me some sample code?

If not, don't worry I will be trying now anyway.
I will let you know how I go and come back with questions :)

Thanks
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24811747
"When the hook starts you would create a char array the size of your secret word(or just a string I suppose"
Uhh... when does a hook start and when does it finish?
0
 
LVL 13

Expert Comment

by:ThievingSix
ID: 24811873
Here's my unit that I used to disable certain keys in windows. When your application starts you call Hookit; When it ends you call UnHookit;

The hook is system wide.
unit uLockHook;
 

interface
 

uses

  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

  Dialogs, StdCtrls;
 

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.

 

Open in new window

0
6 Surprising Benefits of Threat Intelligence

All sorts of threat intelligence is available on the web. Intelligence you can learn from, and use to anticipate and prepare for future attacks.

 
LVL 13

Author Comment

by:rfwoolf
ID: 24811943
Thanks Thieving. I will try and adapt it for my purposes.
Can you explain HC_ACTION?
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24812023
I'm not understanding the technology I'm using which is making it very difficult to alter your solution to mine.
The Callback function is recursive, but I can't understand when to recurse, and when not to.

Essentially I'm trying to do exactly what you proposed, which is a bit difficult if I don't know what is meant by "when the hook starts".
I could really use someone to just do some basic sample code.
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24812124
Thieving Six, I can't understand this, but no matter what I do your hook procedure is called twice. Even if I stop recursion. Put a showmessage('1') at the beginning of your procedure, followed by exit. I get '1' twice.
0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24812210
" When your application starts you call Hookit; When it ends you call UnHookit;"
How do I call something when my application closes? I go to Project -> View Source, and then?
0
 
LVL 13

Expert Comment

by:ThievingSix
ID: 24812322
Sorry for being so vague  =(!

"Can you explain HC_ACTION?"
When nCode is less than 0 windows states that you must end the hook and pass it on. HT_ACTION is just the official way of doing so.

I'll try to explain the code a bit. Windows has something a kin to a list of hooks that applications have defined. When you call SetWindowsHookEx you are essentially adding your hook to that list. When the action occurs such as a keyboard press in our case it goes through the hooklist one at a time. CallNextHookEx basically tells Windows "your done, go to the next one" and passes the information you changed to the next hook. So in a sense it's not recursive, it just is executed like an event.

"Essentially I'm trying to do exactly what you proposed, which is a bit difficult if I don't know what is meant by "when the hook starts"."
What I meant by this is when the callback is entered(e.g. in the HT_ACTION block) you would reading there there.

"but no matter what I do your hook procedure is called twice"
The reason for this is it is executed for the key press and then the key release. The wParam can be one of the following messages: WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, or WM_SYSKEYUP. You can also use Hook ^.Flags to check. If the last bit is set it's being released.

For calling something when your application closes you can either add it to your OnFormClose of your main form or do something like this in the Project main source:


begin

  ShowMessage('Program has started');

  Application.Initialize;

  Application.MainFormOnTaskbar := True;

  Application.CreateForm(TForm10, Form10);

  Application.Run;

  ShowMessage('Program has ended');

end.

Open in new window

0
 
LVL 13

Accepted Solution

by:
ThievingSix earned 500 total points
ID: 24812595
Alright here is some horrible code but it works the way I originally explained it. The code does Hookit; and UnHookit; as I showed above. Form has a list box for debugging, edit box for result, memo to type the test in manually.

This is a very very bad piece of code that will require you modifing it, it did work when I typed: "startthis is a testend" in the memo.
unit Unit12;

 

interface

 

uses

  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

  Dialogs, StdCtrls, Math;

 

const

  WH_KEYBOARD_LL       =  $000D;

  LLKHF_EXTENDED       =  $0001;

  LLKHF_INJECTED       =  $0010;

  LLKHF_ALTDOWN        =  $0020;

  LLKHF_UP             =  $0080;

  CODE_START           =  'START';

  CODE_END             =  'END';
 

type

  tagKBDLLHOOKSTRUCT =  packed record

    vkCode :            DWORD;

    scanCode :          DWORD;

    flags :             DWORD;

    time :              DWORD;

    dwExtraInfo :       Integer;

  end;

  KBDLLHOOKSTRUCT      =  tagKBDLLHOOKSTRUCT;

  PKBDLLHOOKSTRUCT     =  ^KBDLLHOOKSTRUCT;
 

var

  hkHook : HHook;

  BufferStart : String[5];

  BufferEnd : String[3];

  BufferOut : String;

  Recording : Boolean;

  function LowLevelKeyboardProc(nCode: Integer; wParam: Integer; lParam: Integer): Integer; stdcall; export;

  procedure HookIt;

  procedure UnHookIt;
 

implementation
 

uses Unit11;

 

procedure HookIt;

begin

  FillChar(BufferStart[1],5,0);

  FillChar(BufferEnd[1],3,0);

  Recording := False;

  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

  Try

    Hook := Pointer(lParam);

    Case nCode Of

      HC_ACTION:

        begin

          If (Hook^.flags And LLKHF_UP) = 0 Then

            begin

            If Length(BufferStart) = 5 Then

              begin

              If BufferStart = CODE_START Then

                begin

                Recording := True;

              end

              Else

                begin

                BufferStart := Copy(BufferStart,2,4);

                BufferStart := BufferStart + Char(Hook^.vkCode);

                Exit;

              end;

            end

            Else

              begin

              BufferStart := BufferStart + Char(Hook^.vkCode);

              Exit;

            end;

            If Recording Then

              begin

              If Length(BufferEnd) = 3 Then

                begin

                If BufferEnd = CODE_END Then

                  begin

                  Form11.Edit1.Text := Copy(BufferOut,1,Length(BufferOut) - 2);

                  BufferStart := '';

                  BufferEnd := '';

                  BufferOut := '';

                  Recording := False;

                end

                Else

                  begin

                  BufferOut := BufferOut + Copy(BufferEnd,Min(Length(BufferOut),3),3);

                  BufferEnd := Copy(BufferEnd,2,2);

                  BufferEnd := BufferEnd + Char(Hook^.vkCode);

                  If BufferEnd = CODE_END Then

                    begin

                    Form11.Edit1.Text := Copy(BufferOut,1,Length(BufferOut) - 2);

                    BufferStart := '';

                    BufferEnd := '';

                    BufferOut := '';

                    Recording := False;

                    Exit;

                  end;

                end;

              end

              Else

                begin

                BufferEnd := BufferEnd + Char(Hook^.vkCode);

              end;

            end;

          end;

        end;

      end;

  Finally

    Form11.ListBox1.Items.Add(Format('BufferEnd: %s  -  BufferOut: %s',[BufferEnd,BufferOut]));

    Result := CallNextHookEx(hkHook,nCode,wParam,lParam);

  end;

end;

 

end.

Open in new window

0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24812638
That's great, except it outputs the barcode in capitals. That's fine, but isn't there a way to do it with mixed case?
0
 
LVL 13

Expert Comment

by:ThievingSix
ID: 24812861
I only tried that once and that was when I tried checking if the shift key was pressed. Unfortunately it didn't work very well as the shift key would "lag" until the next character or two. What you want is to do something like TranslateMessage.

Actually, this will work:
unit Unit12;

 

interface

 

uses

  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

  Dialogs, StdCtrls, Math;

 

const

  WH_KEYBOARD_LL       =  $000D;

  LLKHF_EXTENDED       =  $0001;

  LLKHF_INJECTED       =  $0010;

  LLKHF_ALTDOWN        =  $0020;

  LLKHF_UP             =  $0080;

  CODE_START           =  'START';

  CODE_END : PChar     =  'END';
 

type

  tagKBDLLHOOKSTRUCT =  packed record

    vkCode :            DWORD;

    scanCode :          DWORD;

    flags :             DWORD;

    time :              DWORD;

    dwExtraInfo :       Integer;

  end;

  KBDLLHOOKSTRUCT      =  tagKBDLLHOOKSTRUCT;

  PKBDLLHOOKSTRUCT     =  ^KBDLLHOOKSTRUCT;
 

var

  hkHook : HHook;

  BufferStart : String[5];

  BufferEnd : String[3];

  BufferOut : String;

  Recording : Boolean;

  function LowLevelKeyboardProc(nCode: Integer; wParam: Integer; lParam: Integer): Integer; stdcall; export;

  procedure HookIt;

  procedure UnHookIt;
 

implementation
 

uses Unit11;

 

procedure HookIt;

begin

  FillChar(BufferStart[1],5,0);

  FillChar(BufferEnd[1],3,0);

  Recording := False;

  hkHook := SetWindowsHookEx(WH_KEYBOARD_LL,@LowLevelKeyboardProc,hInstance,0);

end;

 

procedure UnHookIt;

begin

  UnHookWindowsHookEx(hkHook);

end;
 

function Scan2Ascii(ScanCode: DWORD; Output: PChar): Integer;

var

  Layout : HKL;

  State : TKeyboardState;

  VK : DWORD;

begin

  Result := 0;

  Layout := GetKeyboardLayout(0);

  If GetKeyboardState(State) = False Then Exit;

  VK := MapVirtualKeyEx(ScanCode,1,Layout);

  Result := ToASCIIEx(VK,ScanCode,State,Output,0,Layout);

end;
 

function LowLevelKeyboardProc(nCode: Integer; wParam: Integer; lParam: Integer): Integer; stdcall; export;

var  

  Hook : PKBDLLHOOKSTRUCT;

  bControlKeyDown : Boolean;

  Output : PChar;

begin

  Try

    Hook := Pointer(lParam);

    Case nCode Of

      HC_ACTION:

        begin

          If (Hook^.flags And LLKHF_UP) = 0 Then

            begin

            If Length(BufferStart) = 5 Then

              begin

              If BufferStart = CODE_START Then

                begin

                Recording := True;

              end

              Else

                begin

                BufferStart := Copy(BufferStart,2,4);

                BufferStart := BufferStart + Char(Hook^.vkCode);

                Exit;

              end;

            end

            Else

              begin

              BufferStart := BufferStart + Char(Hook^.vkCode);

              Exit;

            end;

            If Recording Then

              begin

              If Length(BufferEnd) = 3 Then

                begin

                If UpperCase(BufferEnd) = CODE_END Then

                  begin

                  Form11.Edit1.Text := Copy(BufferOut,1,Length(BufferOut) - 2);

                  BufferStart := '';

                  BufferEnd := '';

                  BufferOut := '';

                  Recording := False;

                end

                Else

                  begin

                  BufferOut := BufferOut + Copy(BufferEnd,Min(Length(BufferOut),3),3);

                  BufferEnd := Copy(BufferEnd,2,2);

                  If Scan2Ascii(Hook^.scanCode,Output) <> 0 Then

                    begin

                    BufferEnd := BufferEnd + Output;

                  end;

                  If UpperCase(BufferEnd) = CODE_END Then

                    begin

                    Form11.Edit1.Text := Copy(BufferOut,1,Length(BufferOut) - 2);

                    BufferStart := '';

                    BufferEnd := '';

                    BufferOut := '';

                    Recording := False;

                    Exit;

                  end;

                end;

              end

              Else

                begin

                If Scan2Ascii(Hook^.scanCode,Output) <> 0 Then

                  begin

                  BufferEnd := BufferEnd + Output;

                end;

              end;

            end;

          end;

        end;

      end;

  Finally

    Form11.ListBox1.Items.Add(Format('BufferEnd: %s  -  BufferOut: %s',[BufferEnd,BufferOut]));

    Result := CallNextHookEx(hkHook,nCode,wParam,lParam);

  end;

end;

 

end.

Open in new window

0
 
LVL 13

Author Comment

by:rfwoolf
ID: 24813111
Okay, using your latest code it no longer worked at all, but I can see the Scancode -> Pchar function which is great.
Thanks so much for your help.
0
 
LVL 13

Expert Comment

by:ThievingSix
ID: 24818800
Wow, weird. Ok since your just going to meld that function into the other let me warn you that when checking for the end word it will be case sensitive.
0

Featured Post

Highfive + Dolby Voice = No More Audio Complaints!

Poor audio quality is one of the top reasons people don’t use video conferencing. Get the crispest, clearest audio powered by Dolby Voice in every meeting. Highfive and Dolby Voice deliver the best video conferencing and audio experience for every meeting and every room.

Join & Write a Comment

Objective: - This article will help user in how to convert their numeric value become words. How to use 1. You can copy this code in your Unit as function 2. than you can perform your function by type this code The Code   (CODE) The Im…
Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
Polish reports in Access so they look terrific. Take yourself to another level. Equations, Back Color, Alternate Back Color. Write easy VBA Code. Tighten space to use less pages. Launch report from a menu, considering criteria only when it is filled…
When you create an app prototype with Adobe XD, you can insert system screens -- sharing or Control Center, for example -- with just a few clicks. This video shows you how. You can take the full course on Experts Exchange at http://bit.ly/XDcourse.

758 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

18 Experts available now in Live!

Get 1:1 Help Now