65.9K
CodeProject is changing. Read more.
Home

Recording mouse and keyboard events and playing them back

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.73/5 (7 votes)

Jul 8, 2007

CPOL

1 min read

viewsIcon

68994

downloadIcon

7561

The application records user input events into a file and plays them back when required.

Introduction

This is a sample project used to record keyboard and mouse events from a user in a file and to play them back when required.

Background

The program does not do anything tricky and just uses Windows hooks (the WH_JOURNALRECORD hook to be precise). You can refer to MSDN for more details.

Using the Code

The code is organized into two projects: the application that controls the start and stop of record and play events, the DLL which actually does the job.

For WH_JOURNALRECORD hooks, the DLL may not be required and the core logic can be embedded in the application source itself.

Following is how the application works: for starting recording: click File->Start recording, and then press the Start button on the menu. All the events get recorded in c:\recording.txt. To stop recording, click File->Stop recording.

To play back the recording, click File->Play and then press the Start button on the menu.

Following is the hook procedure that records the events:

// this procedure gets called whenever windows wants us to record some event 
LRESULT CALLBACK RecorderCallProc(int nCode, WPARAM wParam, LPARAM lParam)
{
  EVENTMSG *msg_ptr = NULL;
  static int continue_recording = TRUE;
  
  switch(nCode)
  {
    case HC_SYSMODALON:  /* some system modal dialog is ON. Stop recording */
      continue_recording = FALSE;
      return CallNextHookEx(g_recorder_hook, nCode, wParam, lParam);

    case HC_SYSMODALOFF: /* system modal dialog is OFF, continue recording */
      continue_recording = TRUE;
      return CallNextHookEx(g_recorder_hook, nCode, wParam, lParam);
  }

  if (nCode != HC_ACTION || continue_recording == FALSE)
  { 
  /* Record only on HC_ACTION event when recording is enabled; not now */
    return CallNextHookEx(g_recorder_hook, nCode, wParam, lParam);
  }

  msg_ptr = (EVENTMSG *)lParam;
  
  /* Store the event passed by windows in a file to read back later for play */
  fwrite(msg_ptr, sizeof(EVENTMSG), 1, g_fp);
  fprintf(g_recorder_log, "HWND:%d, MSG:%d, WPARAM:%d, LPARAM:%d\n", 
          msg_ptr->hwnd, msg_ptr->message, 
          msg_ptr->paramH, msg_ptr->paramL);

  return CallNextHookEx(g_recorder_hook, nCode, wParam, lParam);
}

// This proc gets called to fetch next event to play 
__declspec(dllexport) LRESULT CALLBACK PlayerProc(int nCode, WPARAM wParam, LPARAM lParam)
{
  static FILE *fp = NULL;
  static int continue_playing = TRUE;
  static EVENTMSG msg;
  int ret_val = 0;
  static int previous_timeout = 0;
  static int current_timeout = 0;
  int timeout = 0;

  if (fp == NULL)
  { /* The events recorded in procedure below are to be read back now for playing */
    fp = fopen("c:\\recording.txt", "rb");
    ret_val = fread(&msg, sizeof(EVENTMSG), 1, fp);
    previous_timeout = msg.time;
    current_timeout = msg.time;
  }

  switch(nCode)
  {
    case HC_SYSMODALON:  /* some system modal dialog ON. Pause playing */
      continue_playing = FALSE;
      return 0;

    case HC_SYSMODALOFF: /* system modal dialog OFF. continue playing */
      continue_playing = TRUE;
      return 0;

    case HC_SKIP:  /* time to send next event in file to windows */
      if (continue_playing == FALSE)
      {
        return 0;
      }

      ret_val = fread(&msg, sizeof(EVENTMSG), 1, fp);
      fprintf(g_player_log, "HWND:%d, MSG:%d, WPARAM:%d, LPARAM:%d\n", msg.hwnd, msg.message, \
         msg.paramH, msg.paramL);
      previous_timeout = current_timeout;
      current_timeout = msg.time;

      if (ret_val != 1)
      {
        /* End of file. Uninstall the hook */
        fclose(fp);
        fclose(g_player_log);
        fp = NULL;
        UnhookWindowsHookEx(g_player_hook);
        MessageBox(NULL, TEXT("Recording complete"), TEXT("Success"), 0);
        
      }
      return 0;
  }

  if (nCode != HC_GETNEXT || continue_playing == FALSE)
  {
    if (nCode < 0)
    {
      return CallNextHookEx(g_player_hook, nCode, wParam, lParam);
    }
    return 0;
  }
  /* Copy the current event to lparam if action is HC_GETNEXT */
  memcpy((void *)lParam, (void *)&msg, sizeof(msg))


  /* if function returns 0 on HC_GETNEXT, then the event stored in lparam gets
executed immediately. Else, windows sleeps for that many milliseconds and asks
for the same event again. The timestamp of the events is also stored. Use
those to raise an event at appropriate time.*/
  if (current_timeout != previous_timeout)
  {
    timeout = (current_timeout > previous_timeout ? (current_timeout - previous_timeout) : \
     (current_timeout + (0xffffffff - previous_timeout)));
    previous_timeout = current_timeout;
    return timeout;
  }
  else
  {
    return 0;
  }
}

Points of Interest

The program is a very naive application. I found that the following events do not get recorded:

  1. Pressing the Windows key
  2. Typing anything in the Run command (Start->Run)
  3. Windows Search wizard

The Start button is additionally placed in the window so that the mouse pointer is on the same location when starting a recording and when playing a recording. Else, a slight difference in position can cause great havoc (when Windows starts clicking on wrong (adjacent) windows during playback).

History

Initial draft.