?
Solved

Trap keystrokes and log to text file

Posted on 2005-04-02
14
Medium Priority
?
257 Views
Last Modified: 2010-04-05
Hey,
This one should be simple enough.

I want to log everything I type on my computer to a text file.
I will have the program open and in the taskbar, but it will not have focus.
I want to log all characters to a text file and start a new line when the enter key is pressed.

Can someone show me sample code for doing this please?

Thanks
John
0
Comment
Question by:BlackStorm
[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
  • 5
  • 4
  • 2
  • +1
14 Comments
 
LVL 11

Expert Comment

by:shaneholmes
ID: 13690108
Actually it is not simple!

If your program does not have focus, then you are trapping keystrokes at the OS level and not your application.....

sholmes
0
 
LVL 11

Expert Comment

by:shaneholmes
ID: 13690130
basically, for:

Non-Windows NT: you will need to learn to use the SetWindowsHookEx.
Windows NT: you will need to write your own keyboard driver (VxD).

sholmes
0
 

Author Comment

by:BlackStorm
ID: 13690184
Ah right...
I remember I found code to do this around a year ago.
I think it might have been from about.com but not positive.

I will have a look around and see what I can find, I did a lot of searching but everything I foudn was unrelated.

Thanks Shane
John
0
Industry Leaders: 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 15

Accepted Solution

by:
mikelittlewood earned 0 total points
ID: 13690199
This code should handle pretty much most of what you want.
I just made an application to do this and put it in my system tray password protected

I used a listview to hold the current information, and saved text to a database, but you could replace the code and put the current text in a stringlist and do
StringList1.SaveToFile('c:\log.txt') or something

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  // initialise the hook
  Hooked := False;
end;

procedure TfrmMain.mnuOnClick(Sender: TObject);
begin
  {check to see if the Hook is running and don't allow it to be started again}
  if Hooked then
    begin
    Messagebox(frmMain.Handle,'Journal Hook has already been started',
    'No can Restart', MB_OK or MB_ICONQUESTION);
    Exit;
    end;

  JHook := SetWindowsHookEx(WH_JOURNALRECORD , @JHookFunc, hInstance, 0);
  if JHook > 0 then
    begin
      Hooked := True;
      frmMain.Caption := cFormCaption + ' [Hook On]';
    end
    else
      ShowMessage('No Journal Hook availible');
end;

procedure TfrmMain.mnuOffClick(Sender: TObject);
begin
  // turn off the hook
  Hooked := False;
  UnhookWindowsHookEx(JHook);
  JHook := 0;
  frmMain.Caption := cFormCaption + ' [Hook Off]';
end;

procedure TfrmMain.ApplicationEventsMessage(var Msg: tagMSG;
  var Handled: Boolean);
begin
  Handled := False;
  if (Msg.message = WM_CANCELJOURNAL) and Hooked then
    JHook := SetWindowsHookEx(WH_JOURNALRECORD , @JHookFunc, hInstance, 0);
  {IMPORTANT, , whenever the Ctrl+Esc keys are pressed the Journal Hook is canceled
  and a WM_CANCELJOURNAL is sent, so you need to restart the hook}
end;

function JHookFunc(Code:integer; wParam: Longint; var EventStrut: TEVENTMSG): Longint; stdcall;
var
  KeyState1: TKeyBoardState;
  AryChar: Array[0..1] of Char;
  Count: Integer;
  VirtKey, ScanCode: Cardinal;
  S:      string;
begin
  {this is the Hook function that gets ALL of the Keyboard events}
  Result := CallNextHookEx(JHook, Code, wParam, Integer(@EventStrut));
  {go ahead and Call nextHook, although it's not really needed in a Journal Hook}
  if Code < 0 then Exit;
  if Code = HC_ACTION then
    begin
  {HC_ACTION means that an event has occured}
    if (EventStrut.paramL = 17923) then {17923 is for Ctrl+Break}
      begin
      {It is recomended to end hooks with Ctrl+Break}
      frmMain.mnuOffClick(frmMain);  {Ctrl+Break will End the Journal record}
      Exit;
      end;

    if EventStrut.message = WM_KEYDOWN then
    begin
      {the LOBYTE LOWORD of papamL has the Virtual Key Code}
      VirtKey := LOBYTE(LOWORD(EventStrut.paramL));
      {the HIBYTE LOWORD of paramL has the Scan Code}
      ScanCode := HIBYTE(LOWORD(EventStrut.paramL));
      GetKeyboardState(KeyState1);
      {to find out if the Shift key is down we get the KeyboardState}
      Count := ToAscii(VirtKey,ScanCode, KeyState1, AryChar, 0);
      {ToAscii( )  function is like TranslateMessage( ) and converts the Virtual Key to a Char}

      if Count = 1 then
      begin
        // get the current focused application title
        S := GetActiveWindowTitle;
        // add the keypressed to the listview
        frmMain.VisualizeKey(AryChar[0], S);
      end;

    end;
  end;
end;

procedure TfrmMain.VisualizeKey(Key: Char; AppName: string);
var
  B:Boolean;
  I: Integer;
  ListItm: TListITem;
begin
  with lvLog do
  begin
    B := False;
    // loop through the listview
    for I := 0 to (Items.Count-1) do
    begin
      // see if the current application exists in our view
      if (AppName=Items[I].Caption) then
      begin
        B := True;
        Break; // for I
      end;
    end;
    if B then
    begin
      Items[I].SubItems[0] := Items[I].SubItems[0] + Key;

      // now check the current length of the selected application line and
      // see if we need to save a log to the database
      if Length( Trim(Items[I].SubItems[0]) ) > 200 then
      begin

        // DO YOU SAVING TO FILE HERE

        // clear the value in the selected item
        Items[I].SubItems[0] := '';
      end;
    end
    else
    begin
      ListItm := Items.Add;
      ListItm.Caption := AppName;
      ListItm.SubItems.Add(Key);
    end;
  end;
end;
0
 

Author Comment

by:BlackStorm
ID: 13690251
Hey mike,
Thanks very much for doing that!
I will give you more points than I have put here.

Did you test that code?

I am getting a lot of errors when I try run it.
Mostly Undeclared identifier and a few others:

[Warning] Unit1.pas(134): Comparing signed and unsigned types - widened both operands
if (AppName=Items[I].Caption) then

[Error] Unit1.pas(146): There is no overloaded version of 'Trim' that can be called with these arguments
if Length( Trim(Items[I].SubItems[0]) ) > 200 then


Thanks
John
0
 
LVL 15

Expert Comment

by:mikelittlewood
ID: 13691008
I have only taken out a few procedures from the application I wrote, but they are the most useful ones. I havent given you code for an entire application up there.
Do you think you will be able to insert the code I have given you into an application you can write. Are you able to modify it enough or would you like me to send you the entire application I wrote minus all the database stuff it is doing. If you want me to send it you will have to give me time to re-code it a little and email it to you.
0
 

Author Comment

by:BlackStorm
ID: 13691774
Hey Mike,
I would really appreciate if you could send the program.
My email is {email address removed - ai, cs admin}

Thanks very much!
John
0
 
LVL 15

Expert Comment

by:mikelittlewood
ID: 13691883
Ok gimme till tomorrow to send it to you
0
 
LVL 34

Expert Comment

by:Slick812
ID: 13693674
hello BlackStorm , Here is some code for a library program that uses the WH_KEYBOARD  hook to get the keyboard messages. I feel like the key hook is better for this sort of thing, than the journal hook. The Library DLL will get called into every process running in the windows message system, so there will be several instances of this DLL running after the hook is set, so i placed a memory mapped file in it to keep the Forms handle in so each library can send it a WM_USER message with the Charater that was typed. When the program gets the message, it will write a text character to the log file or a line break.

Here is the code for a key logger in the library program -




library LogKey;

uses
  Windows, Messages;

{$R *.RES}


const
mapFileName = 'yu7bx(4+jHg3z';

var
Hooked: Boolean;
pMsgForm: PDWORD;
hKeyHook, hMemFile: Cardinal;



function KeyHookFunc(Code, VirtualKey, KeyStroke: Integer): Integer; stdcall;
var
KeyState1: TKeyBoardState;
AryChar: Array[0..1] of Char;
Count: Integer;
begin
Result := 0;
if Code = HC_NOREMOVE then Exit;
Result := CallNextHookEx(0, Code, VirtualKey, KeyStroke);
if (pMsgForm <> nil) and  ((KeyStroke and (1 shl 30)) <> 0) then
  begin
  GetKeyboardState(KeyState1);
  Count := ToAscii(VirtualKey,KeyStroke, KeyState1, AryChar, 0);
  if Count = 1 then
  if Ord(AryChar[0]) = 13 then
    PostMessage(pMsgForm^, WM_USER+5432, 10, $0A0D)
    else
    PostMessage(pMsgForm^, WM_USER+5432, 20, Ord(AryChar[0]));
  end;
end;

function StartHook(hMsgWnd: Cardinal): Integer; export;
begin
Result := 1;
if Hooked then Exit;
if not IsWindow(hMsgWnd) then
  begin
  Result := 4;
  Exit;
  end;
 
hMemFile := CreateFileMapping(MAXDWORD, nil,PAGE_READWRITE,0,SizeOf(pMsgForm), mapFileName);
pMsgForm := MapViewOfFile(hMemFile, FILE_MAP_WRITE, 0, 0, 0);
if pMsgForm = nil then
  begin
  CloseHandle(hMemFile);
  Result := 3;
  Exit;
  end;
hKeyHook := SetWindowsHookEx(WH_KEYBOARD, KeyHookFunc, hInstance, 0);
if hKeyHook > 0 then
  begin
  pMsgForm^ := hMsgWnd;
  Hooked := True;
  Result := 0;
  end else
  begin
  UnmapViewOfFile(pMsgForm);
  CloseHandle(hMemFile);
  pMsgForm := nil;
  Result := 2;
  end;
end;

function StopHook: Boolean; export;
begin
if pMsgForm <> nil then
  begin
{be sure to release your MemMap file}
  UnmapViewOfFile(pMsgForm);
  CloseHandle(hMemFile);
  pMsgForm := nil;
  end;
if Hooked then
  begin
  Result := UnhookWindowsHookEx(hKeyHook);
  end else
  Result := True;
if Result then
Hooked := False;
end;


procedure EntryProc(Reason : Cardinal);
begin
if (Reason = Dll_Process_Attach) then
  begin
{get your memory file at startup, hMemFile will be Zero if not there}
  hMemFile := OpenFileMapping(FILE_MAP_WRITE, False, mapFileName);
  if hMemFile > 0 then
    begin
    pMsgForm := MapViewOfFile(hMemFile, FILE_MAP_WRITE, 0, 0, 0);
    end;
  end;

 if (Reason = Dll_Process_Detach) then
 begin
 if pMsgForm <> nil then
  begin
  {be sure to release your MemMap Pointer}
  UnmapViewOfFile(pMsgForm);
  CloseHandle(hMemFile);
  end;
 if hKeyHook <> 0 then
   UnhookWindowsHookEx(hKeyHook);
 end;
end;

exports
  StartHook,
  StopHook;

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


 = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

here is some code for your program to start and stop the Key hook, there is a Form1 OnCreate event, a window message procedure and 2 button click events -




private
    { Private declarations }
    hKLogLib: THandle;
    LogFile: TextFile;
    procedure KeyLogMess(var Message: TMessage); message WM_USER+5432;



procedure TForm1.FormCreate(Sender: TObject);
begin
hKLogLib := 0;
end;


procedure TForm1.KeyLogMess(var Message: TMessage);
begin
// this gets the message form one of the DLL's and writes Char to LogFile
if Message.WParam = 10 then // wParam as 10 is charage return
  WriteLn(LogFile)
  else // wParam as 20 is text character
  if (Message.WParam = 20) and (Message.LParam < 256) then
    Write(LogFile, Char(Message.LParam));
end;


procedure TForm1.sbut_StartHookClick(Sender: TObject);
var
StartHook: function(hMsgWnd: Cardinal): Integer;
begin
// button click to start Key Hook
if hKLogLib = 0 then
  hKLogLib := LoadLibrary('LogKey.dll');
if hKLogLib = 0 then
  begin
  ShowMessage('ERROR - Could not load library');
  Exit;
  end;

@StartHook := GetProcAddress(hKLogLib, 'StartHook');
if @StartHook = nil then
  begin
  ShowMessage('StartHook was not located');
  FreeLibrary(hKLogLib);
  hKLogLib := 0;
  Exit;
  end;

if StartHook(Handle) <> 0 then
  begin
  ShowMessage('StartHook was not Successful');
  FreeLibrary(hKLogLib);
  hKLogLib := 0;
  Exit;
  end;
AssignFile(LogFile, 'E:\Log1.log');
Rewrite(LogFile);
//WriteLn(LogFile, 'New Log');
end;


procedure TForm1.but_StopHookClick(Sender: TObject);
var
StopHook: function: Boolean;
begin
// button click to stop hook
if hKLogLib = 0 then Exit;
CloseFile(LogFile);
@StopHook := GetProcAddress(hKLogLib, 'StopHook');
if @StopHook = nil then
  ShowMessage('StopHook was not located')
  else
  if not StopHook then
  ShowMessage('StopHook was not successful')
  else

FreeLibrary(hKLogLib);
hKLogLib := 0;
end;


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

maybe this can help you out
ask questions if you need more
0
 
LVL 15

Expert Comment

by:mikelittlewood
ID: 13695645
Hi John, Im trying to email you this code.
What is the format of your email address?

john @ [nospam] hostingrefuge.com
is [nospam] meant to be nospam. ?
0
 

Author Comment

by:BlackStorm
ID: 13710022
Hey Mike,
That is exactly what I was looking for, thank you very much.
 
I really appreciate your help!

John

PS. Sorry Slick, I didn't get a chance to try your code because Mike emailed me his program.
0
 
LVL 34

Expert Comment

by:Slick812
ID: 13710920
thats OK John,

I beleive that the rules here at EE  say something about  Not  using E-mail to do responces or answers to questions. .
The purpose here at EE is to have searchable code solutions for people to look at again and again. . if you E-Mail your solutions then others who are searching for a soution will not be able to see the code for the EE question that might help them. . .

you may consider posting some form of code you used for the solution here in this question so others may be helped by it; ;

Good Luck !
0
 
LVL 15

Expert Comment

by:mikelittlewood
ID: 13718482
I did post the code that I sent BlackStorm back near the beginning of the question.
All I did was send him it again with the form as well.
0

Featured Post

Technology Partners: 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

Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
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…
Michael from AdRem Software explains how to view the most utilized and worst performing nodes in your network, by accessing the Top Charts view in NetCrunch network monitor (https://www.adremsoft.com/). Top Charts is a view in which you can set seve…
Sometimes it takes a new vantage point, apart from our everyday security practices, to truly see our Active Directory (AD) vulnerabilities. We get used to implementing the same techniques and checking the same areas for a breach. This pattern can re…
Suggested Courses

777 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