OS/2 2.X Journal Hook Summary

Abstract
A few excerpts on how to use the Journal Hooks. This can enable recording and playback of mouse and keyboard events on a OS/2 2.x system.  /************************************************************************/ /* Copyright 1993 IBM Corporation and CI, Software & Graphic Arts, Inc. */ /*                                                                     */ /*                        All Rights Reserved                           */ /*                                                                     */ /* Permission to use, copy, modify, and distribute this software for    */ /* any purpose and without fee is hereby granted, provided that the    */ /* above copyright notice appear in all copies and that both the       */ /* copyright notice and this permission notice appear in supporting    */ /* documentation, and that the names of IBM or CI not be used in       */ /* advertising or publicity pertaining to distribution of the software */ /* without specific, written prior permission. */ /*                                                                     */ /* IBM & CI DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,       */ /* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN */ /* NO EVENT SHALL IBM OR CI BE LIABLE FOR ANY SPECIAL, INDIRECT OR     */ /* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS */ /* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE*/ /* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE  */ /* USE OR PERFORMANCE OF THIS SOFTWARE. */ /************************************************************************/ /*                                                                     */ /* The following information represents summaries from an upcoming book,*/ /* "Insider Information: Developing, Debugging and Testing OS/2 2.x    */ /* Applications", by Ivan Biddles and Kelvin Lawrence [QED Publishing]. */ /*                                                                     */ /************************************************************************/ 

Hook Procedures - general rules

 * Include the EXPENTRY linkage keyword on all hook procedures.
 * As with an interrupt handler, the time spent in a hook procedure must be kept to an absolute minimum. (No I/O or Win calls - use a separate thread.)
 * The playback hook procedure could be called AFTER WinReleaseHook, so leave a dummy mouse move message to be returned.

Journal Playback Hook
 ULONG  EXPENTRY  MyJournalPlaybackHookProc (                                             HAB    hab,                                             BOOL   fSkip,                                             PQMSG  lpqmsg                                            ) 
 * hab:is the application thread anchor block
 * fSkip:is TRUE if the last message has been activated, and a new message is to be prepared
 * FALSE if the same message is to be passed again


 * lpqmsg:is a pointer to the QMSG structure to be filled

Message timing
Whenever your hook procedure provides an event to PM, you have to ensure that the time at which the event is due to be dispatched is kept updated. The return value from your procedure has to reflect the number of milliseconds until the message is due.

Sample Hook Procedure
This procedure assumes that there is a second thread which is reading, parsing and formatting the events, and placing them in a circular array accessed via two indices, ulQmsgBottom and ulQmsgTop.

The module handle used in the call to WinRelease hook is a variable set by the playback initiation routine, which it gets from a call to the DosQueryModuleHandle API. 
 * 1) define MAXQMSGBUFFERSIZE      5000

static      QMSG            qmsgBuffer      [MAXQMSGBUFFERSIZE]; static      ULONG           ulQmsgBottom    = 0; static      ULONG           ulQmsgTop       = 0; static      HMODULE         hModule;

ULONG EXPENTRY MyJournalPlaybackHookProc (                                         HAB   hab,                                          BOOL  fSkip,                                          PQMSG lpqmsg                                          ) {  static       QMSG            qmsgMouse; static      QMSG            qmsgCurrent; static      LONG            lLastMsgTime            = 0L; static      LONG            lCurrentMsgTime         = 0L; static      LONG            lMsgDelay               = 0L; static      LONG            lDelayRemaining         = 0L; static      LONG            lLastSequencedEventTime = 0L; /*--*/               /* Store the passed time as the current message time    */ /*--*/                       /*--*/                        /* Note: if fSkip is TRUE, the lpqmsg pointer   */ /*      will be NULL. */                       /*--*/   if (lpqmsg) { lCurrentMsgTime = lpqmsg->time; }               /*--*/                /* The system requests that a NEW message be prepared   */ /*--*/  if (fSkip) { /*--*/                       /* No more messages on queue - release the hook */ /*--*/     if (ulQmsgBottom == ulQmsgTop) { WinReleaseHook (                        hab,                         0L,                         HK_JOURNALPLAYBACK,                         (PFN) MyJournalPlaybackHookProc,                         hModule                        ); /*--*/                               /* Set a mouse move "in place" message  */ /*--*/        memcpy (&qmsgCurrent, &qmsgMouse, sizeof (QMSG)); }                       /*--*/                        /* Prepare and pass the next message            */ /*--*/     else { /*--*/                               /* Update the time of last message      */ /*--*/        lLastMsgTime = lLastSequencedEventTime; /*--*/                               /* Copy current message from the queue  */ /*--*/        memcpy (&qmsgCurrent, &(qmsgBuffer [ulQmsgBottom]), sizeof (QMSG)); /*--*/                               /* Update the current mouse position so */ /* that the mouse stays put when we    */ /* playback                            */ /*--*/        if (qmsgCurrent.msg == WM_MOUSEMOVE) { qmsgMouse.mp1  = qmsgCurrent.mp1; qmsgMouse.mp2  = qmsgCurrent.mp2; qmsgMouse.ptl.x = qmsgCurrent.ptl.x;           qmsgMouse.ptl.y = qmsgCurrent.ptl.y;         } /*--*/                               /* Update (and wrap) the queue pointer  */ /*--*/        ulQmsgBottom++; if (ulQmsgBottom >= MAXQMSGBUFFERSIZE) { ulQmsgBottom=0L; }                               /*--*/                                /* New message - so set up the delay    */ /*              timing variables       */ /*--*/        lMsgDelay               = qmsgCurrent.time; lDelayRemaining        = lMsgDelay - (lCurrentMsgTime - lLastMsgTime); if (lDelayRemaining < 0) { lDelayRemaining = 0L; }        lLastSequencedEventTime = lCurrentMsgTime + lDelayRemaining; qmsgCurrent.time       = lLastSequencedEventTime; }  }                 /*--*/                /* The system requests a PEEK at the CURRENT message    */ /*--*/  else { /*--*/                       /* Initialize the variables on the first pass   */ /*--*/     if (bFirstTimePlaybackHook) { bFirstTimePlaybackHook = 0; lLastMsgTime           = 0L; lCurrentMsgTime        = 0L; lMsgDelay              = 0L; lDelayRemaining        = 0L; lLastSequencedEventTime = 0L; /*--*/                               /* Initialize the default mouse move    */ /*--*/        qmsgMouse.hwnd  = HWND_DESKTOP; qmsgMouse.msg  = WM_MOUSEMOVE; qmsgMouse.mp1  = (MPARAM) 0; qmsgMouse.mp2  = (MPARAM) 0; qmsgMouse.time = 0L; qmsgMouse.ptl.x = 0L; qmsgMouse.ptl.y = 0L; /*--*/                               /* Send the same message as before      */ /*--*/        if (ulQmsgBottom == ulQmsgTop) { memcpy (&qmsgCurrent, &qmsgMouse, sizeof (QMSG)); }                               /*--*/                                /* Use the next message in the queue    */ /*--*/        else { memcpy (&qmsgCurrent, &(qmsgBuffer[ulQmsgBottom]), sizeof (QMSG)); /*--*/                                       /* New message - so set up the  */ /*     delay timing variables  */ /*--*/           lMsgDelay               = qmsgCurrent.time; lDelayRemaining        = lMsgDelay - (lCurrentMsgTime - lLastMsgTime); if (lDelayRemaining < 0) { lDelayRemaining = 0L; }           lLastSequencedEventTime = lCurrentMsgTime + lDelayRemaining; qmsgCurrent.time       = lLastSequencedEventTime; }     }                        /*--*/                        /* Copy current message to the passed pointer   */ /*--*/     if (lpqmsg) { memcpy (lpqmsg, &qmsgCurrent, sizeof (QMSG)); }  }                /*--*/                /* Update & return the remaining delay for this message */ /*--*/  lDelayRemaining = lMsgDelay - (lCurrentMsgTime - lLastMsgTime); if (lDelayRemaining < 0 ) { lDelayRemaining = 0L; }  return ((ULONG) delayRemaining); } 

Journal Record Hook
 VOID EXPENTRY  MyJournalRecordHookProc (                                         HAB    hab,                                         PQMSG  lpqmsg                                        ) hab    is  the application thread anchor block lpqmsg is  a pointer to the QMSG structure containing the event 

Sample Hook Procedure
This procedure assumes that there is a second thread which is formatting and writing the events to disk. The hook procedure places them in a circular array accessed via two indices, ulQmsgBottom and ulQmsgTop.

To filter out mouse messages (you'll get a mass of messages otherwise), set bMouseCritical false. 
 * 1) define MAXQMSGBUFFERSIZE      5000

static      QMSG            qmsgBuffer      [MAXQMSGBUFFERSIZE]; static      ULONG           ulQmsgBottom    = 0; static      ULONG           ulQmsgTop       = 0; static      BOOL            bMouseCritical  = FALSE;  Note that the hwnd you receive is not significant at this point in the input stream.  VOID EXPENTRY  MyJournalRecordHookProc (                                         HAB    hab,                                         PQMSG  lpqmsg                                        ) {               /*--*/                /* Filter out mouse moves unless required               */ /*--*/  if (bMouseCritical || lpqmsg->msg != WM_MOUSEMOVE) { memcpy (& (qmsgBuffer [ulQmsgTop]), lpqmsg, sizeof (QMSG)); /*--*/                       /* Set the hwnd to null since it is useless at  */ /* this point. */                       /*--*/      qmsgBuffer[ulQmsgTop].hwnd = 0L; /*--*/                       /* Update the buffer pointer (with wraparound)  */ /*--*/     ulQmsgTop++; if (ulQmsgTop >= MAXQMSGBUFFERSIZE) { ulQmsgTop = 0L; }                       /*--*/                        /* Circular queue collision situation - the     */ /* buffer may need to be expanded              */ /*--*/     if (ulQmsgTop == ulQmsgBottom) { /*--*/                               /* Output a message - piped to a file   */ /*--*/        fprintf (stderr, "-- Circular Queue collision: expand MAXQMSGBUFFERSIZE"); }  } } 