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

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 :)
LVL 13
rfwoolfAsked:
Who is Participating?

Improve company productivity with a Business Account.Sign Up

x
 
ThievingSixConnect With a Mentor Commented:
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
 
FactorBCommented:
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
 
rfwoolfAuthor Commented:
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
Free Tool: ZipGrep

ZipGrep is a utility that can list and search zip (.war, .ear, .jar, etc) archives for text patterns, without the need to extract the archive's contents.

One of a set of tools we're offering as a way to say thank you for being a part of the community.

 
bokistCommented:
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
 
rfwoolfAuthor Commented:
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
 
ThievingSixCommented:
-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
 
rfwoolfAuthor Commented:
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
 
rfwoolfAuthor Commented:
"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
 
ThievingSixCommented:
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
 
rfwoolfAuthor Commented:
Thanks Thieving. I will try and adapt it for my purposes.
Can you explain HC_ACTION?
0
 
rfwoolfAuthor Commented:
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
 
rfwoolfAuthor Commented:
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
 
rfwoolfAuthor Commented:
" 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
 
ThievingSixCommented:
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
 
rfwoolfAuthor Commented:
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
 
ThievingSixCommented:
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
 
rfwoolfAuthor Commented:
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
 
ThievingSixCommented:
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
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.