OS/2 2.X Journal Hook Summary

From EDM2
Jump to: navigation, search

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.

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

#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");
      }
   }
}