Solved

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

Posted on 2009-07-09
19
3,843 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
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
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
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
 
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

Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
In my programming career I have only very rarely run into situations where operator overloading would be of any use in my work.  Normally those situations involved math with either overly large numbers (hundreds of thousands of digits or accuracy re…
How to Install VMware Tools in Red Hat Enterprise Linux 6.4 (RHEL 6.4) Step-by-Step Tutorial

740 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