home *** CD-ROM | disk | FTP | other *** search
/ Microsoft Programmer's Library 1.3 / Microsoft-Programers-Library-v1.3.iso / sampcode / win_lrn / hooks / journal / journal.c < prev    next >
Encoding:
C/C++ Source or Header  |  1988-02-22  |  20.1 KB  |  586 lines

  1. /*  Journal - Dynamic Link Library Demonstrating Windows 2.0 Journal Hooks.
  2. *
  3. *   Programmer:
  4. *       Eric Fogelin
  5. *       Microsoft Technical Support
  6. *
  7. *   Purpose:
  8. *       DLL entry points and hook function call-backs.
  9. *
  10. *   Functions:
  11. *       StartRecord() - Entry point for initiating message recording to file.
  12. *       StopRecord()  - Entry point for halting recording.
  13. *       StartPlay()   - Entry point for initiating message playback from file.
  14. *       StopPlay()    - Entry point to halt message playback.
  15. *       Record()      - Call-back for WH_JOURNALRECORD hook function.
  16. *       Play()        - Call-back for WH_JOURNALPLAYBACK hook function.
  17. *       LIBINIT.ASM   - MASM initialization code.  Used to get library
  18. *                       instance handle.
  19. *
  20. *   Description:
  21. *       Windows 2.0 introduces new hook functions, WH_JOURNALRECORD and
  22. *       WH_JOURNALPLAYBACK, which hook into the system message queue
  23. *       allowing keyboard and mouse messages to be recorded for later
  24. *       playback.  A natural use of these functions is for creating
  25. *       demos and test suites, though other imaginative uses must also
  26. *       exist.  This DLL could be easily referenced by any application
  27. *       to quickly add a demo mode.  Note: Use extreme care when developing
  28. *       a demo file.  Window position may change, overlapped window
  29. *       ordering may be different, and display device may vary.  This
  30. *       will impact on demos using the mouse to open windows, move
  31. *       windows, manipulate dialog boxes, etc.  Note:  Journal hooks
  32. *       are disabled by system modal dialog/message boxes.  This is a
  33. *       design feature that must be taken into consideration.
  34. *
  35. *   Limits:
  36. *       Very little error checking is used.  This should be added to any
  37. *       "real" app.  I have indicated areas that require error checking.
  38. *       The hook functions read and write directly to disk via low-level
  39. *       functions, read() and write().  This impacts on performance.
  40. *       Using a 512 or 1024 byte buffer is recommended.  Remember, do
  41. *       not use C run-time buffered stream I/O functions.
  42. *
  43. *   History:
  44. *       2/15/87    - Initial version
  45. *       2/22/87    - Cleaned up
  46. *       2/22/87    - Documentation
  47. */
  48.  
  49. #include "windows.h"
  50. #include "memory.h"
  51. #include "dos.h"
  52.  
  53. /* File used to record/play messages */
  54. #define FILENAME "DIARY.BIN"
  55.  
  56. /* System queue message structure */
  57. typedef struct sysmsg {
  58.     unsigned message;
  59.     WORD wParam1;
  60.     WORD wParam2;
  61.     DWORD lParam;
  62. } SYSMSG;
  63. typedef SYSMSG FAR *LPSYSMSG;
  64.  
  65. /* Static definitions for hooks and chains */
  66. FARPROC lpprocRecord;
  67. FARPROC lplpfnNextRecord;
  68. FARPROC lpprocPlay;
  69. FARPROC lplpfnNextPlay;
  70.  
  71. /* Global variables (Defined in DLL's DGROUP) */
  72. HANDLE          hFile;
  73. OFSTRUCT        of;
  74. SYSMSG          bufMsg;
  75. char far        *lpTmp;
  76. int             nNumMsg, nMsgCtr;
  77. BOOL            bRecord, bPlay;
  78. DWORD           dwBaseTime;
  79.  
  80. /* Handle to the DLL instance from Libinit.asm */
  81. extern HANDLE   LIBINST;
  82.  
  83. /* Function proto-types */
  84. DWORD   FAR PASCAL Record( int, WORD, LPSYSMSG );
  85. DWORD   FAR PASCAL Play( int, WORD, LPSYSMSG );
  86. void    FAR PASCAL StartRecord();
  87. void    FAR PASCAL StopRecord();
  88. void    FAR PASCAL StartPlay();
  89. void    FAR PASCAL StopPlay();
  90.  
  91. /* Undocumented Kernel Function proto-types.  See C run-time doc for
  92.    description of related functions and their parameters. */
  93. int     FAR PASCAL _lclose( int );
  94. int     FAR PASCAL _llseek( int, long, int );
  95. int     FAR PASCAL _lread( int, LPSTR, int );
  96. int     FAR PASCAL _lwrite( int, LPSTR, int );
  97.  
  98. /*  StartRecord() - Set WH_JOURNALRECORD Hook.
  99. *
  100. *   Programmer:
  101. *       Eric Fogelin
  102. *       Microsoft Technical Support
  103. *
  104. *   Purpose:
  105. *       Initialize for and set WN_JOURNALRECORD Hook.
  106. *
  107. *   Arguments:
  108. *       none
  109. *
  110. *   Return Value:
  111. *       void
  112. *
  113. *   Globals (modified):
  114. *       hFile            - Handle to write-only file.
  115. *       bRecord          - Boolean indicating state of Record hook.
  116. *       dwBaseTime       - Reference system time when recording started.
  117. *       lpprocRecord     - Procedure-instance address of call-back fcn.
  118. *       lplpfnNextRecord - Procudure-instance address of any previous
  119. *                          Record hook.  Note: Chaining may not be
  120. *                          applicable.
  121. *
  122. *   Globals (referenced):
  123. *       LIBINST       - Handle to instance for LIBINIT.ASM init. code.
  124. *
  125. *   Description:
  126. *       Isolates the application from the actual hooking and unhooking
  127. *       details.  Allows the application to call high-level routines
  128. *       which take care of the dirty work.
  129. *
  130. *   Limits:
  131. *       Insufficient error checking.  See code for comments.
  132. *
  133. *   History:
  134. *       2/15/87    - Initial version
  135. *       2/22/87    - Cleaned up
  136. *       2/22/87    - Documentation
  137. */
  138. void FAR PASCAL StartRecord()
  139. {
  140.     /* Do not allow recording while playing! DONE! */
  141.     if (bPlay) {
  142.        MessageBox( GetFocus(), (LPSTR)"Currently Playing!", (LPSTR)NULL, MB_OK);
  143.        return;
  144.     }
  145.  
  146.     /* Create write-only file, initialize OFSTRUCT, close. Add error checking */
  147.     hFile = OpenFile( (LPSTR)FILENAME, &of, OF_CREATE | OF_WRITE | OF_EXIST );
  148.  
  149.     /* Initialize: Record on, and Base Time. */
  150.     bRecord = TRUE;
  151.     dwBaseTime = GetTickCount();
  152.  
  153.     /* Set the Journal Record hook.  Add error checking! */
  154.     lpprocRecord = MakeProcInstance( (FARPROC)Record, LIBINST);
  155.     lplpfnNextRecord = SetWindowsHook( WH_JOURNALRECORD, lpprocRecord );
  156.  
  157.     return;
  158. }
  159. /*
  160. */
  161.  
  162. /*  StopRecord() - Unhook WH_JOURNALRECORD Hook.
  163. *
  164. *   Programmer:
  165. *       Eric Fogelin
  166. *       Microsoft Technical Support
  167. *
  168. *   Purpose:
  169. *       Stop further recording by unhooking recording call-back fcn.
  170. *
  171. *   Arguments:
  172. *       none
  173. *
  174. *   Return Value:
  175. *       void
  176. *
  177. *   Globals (modified):
  178. *       bRecord          - Boolean indicating state of Record hook.
  179. *
  180. *   Globals (referenced):
  181. *       lpprocRecord     - Procedure-instance address of call-back fcn.
  182. *
  183. *   Description:
  184. *       Isolates the application from the actual hooking and unhooking
  185. *       details.  Allows the application to call high-level routines
  186. *       which take care of the dirty work.
  187. *
  188. *   Limits:
  189. *       What to do if UnhookWindowsHook fails?  Add error checking!
  190. *
  191. *   History:
  192. *       2/15/87    - Initial version
  193. *       2/22/87    - Cleaned up
  194. *       2/22/87    - Documentation
  195. */
  196. void FAR PASCAL StopRecord()
  197. {
  198.     /* Only unhook if currently hooked. */
  199.     if (bRecord)
  200.        UnhookWindowsHook( WH_JOURNALRECORD, lpprocRecord );
  201.     /* Let the world know that hook is off */
  202.     bRecord = FALSE;
  203.     return;
  204. }
  205. /*
  206. */
  207.  
  208. /*  StartPlay() - Set WH_JOURNALPLAYBACK Hook.
  209. *
  210. *   Programmer:
  211. *       Eric Fogelin
  212. *       Microsoft Technical Support
  213. *
  214. *   Purpose:
  215. *       Initialize for and set WN_JOURNALPLAYBACK Hook.
  216. *
  217. *   Arguments:
  218. *       none
  219. *
  220. *   Return Value:
  221. *       void
  222. *
  223. *   Globals (modified):
  224. *       hFile          - Handle to read-only file.
  225. *       nNumMsg        - Number of messages recorded (in file).
  226. *       nMsgCtr        - Initialize message counter to zero.
  227. *       bPlay          - Boolean indicating state of Play hook.
  228. *       dwBaseTime     - Reference system time when playing started.
  229. *       lpprocPlay     - Procedure-instance address of call-back fcn.
  230. *       lplpfnNextPlay - Procudure-instance address of any previous
  231. *                        Play hook.  Note: Chaining may not be
  232. *                        applicable.
  233. *
  234. *   Globals (referenced):
  235. *       bRecord       - Boolean indicating state of Record hook (modified
  236. *                       if StopRecord() called).  See code.
  237. *       LIBINST       - Handle to instance for LIBINIT.ASM init. code.
  238. *
  239. *   Description:
  240. *       Isolates the application from the actual hooking and unhooking
  241. *       details.  Allows the application to call high-level routines
  242. *       which take care of the dirty work.
  243. *
  244. *   Limits:
  245. *       Insufficient error checking.  See code for comments.
  246. *
  247. *   History:
  248. *       2/15/87    - Initial version
  249. *       2/22/87    - Cleaned up
  250. *       2/22/87    - Documentation
  251. */
  252. void FAR PASCAL StartPlay()
  253. {
  254.     /* Open file so the OFSTRUCT is initialized.  Add error checking! */
  255.     hFile = OpenFile( (LPSTR)FILENAME, &of, OF_READ );
  256.     /* Read the first message into buffer.  Required if HC_GETNEXT is
  257.        first call to Playback hook function.  Must have message ready! */
  258.     _llseek( hFile, 0L, 0 );
  259.     _lread( hFile, (LPSTR)&bufMsg, sizeof(SYSMSG) );
  260.     /* How many messages are stored? */
  261.     nNumMsg = _llseek( hFile, 0L, 2 ) / sizeof(SYSMSG);
  262.     /* Don't leave files open long */
  263.     _lclose( hFile );
  264.  
  265.     /* If no messages were recorded, none to play back. DONE! */
  266.     if (!nNumMsg) {
  267.        MessageBox( GetFocus(), (LPSTR)"Nothing Recorded!", (LPSTR)NULL, MB_OK );
  268.        return;
  269.     }
  270.  
  271.     /* Trick: Allows infinite loop to be set!  One can record the steps
  272.        required to start playing.  This code turns off further recording. */
  273.     if (bRecord)
  274.        StopRecord();
  275.  
  276.     /* Initialize: First Message, Play on, and Base Time. */
  277.     nMsgCtr = 0;
  278.     bPlay = TRUE;
  279.     dwBaseTime = GetTickCount();
  280.  
  281.     /* Set the Journal Playback hook.  Add error checking! */
  282.     lpprocPlay = MakeProcInstance( (FARPROC)Play, LIBINST);
  283.     lplpfnNextPlay = SetWindowsHook( WH_JOURNALPLAYBACK, lpprocPlay );
  284.  
  285.     return;
  286. }
  287. /*
  288. */
  289.  
  290. /*  StopPlay() - Unhook WH_JOURNALPLAYBACK Hook.
  291. *
  292. *   Programmer:
  293. *       Eric Fogelin
  294. *       Microsoft Technical Support
  295. *
  296. *   Purpose:
  297. *       Stop further calls to playback function, Play(), by unhooking.
  298. *
  299. *   Arguments:
  300. *       none
  301. *
  302. *   Return Value:
  303. *       void
  304. *
  305. *   Globals (modified):
  306. *       bPlay          - Boolean indicating state of Play hook.
  307. *
  308. *   Globals (referenced):
  309. *       lpprocPlay     - Procedure-instance address of call-back fcn.
  310. *
  311. *   Description:
  312. *       Isolates the application from the actual hooking and unhooking
  313. *       details.  Allows the application to call high-level routines
  314. *       which take care of the dirty work.
  315. *
  316. *   Limits:
  317. *       What to do if UnhookWindowsHook fails?  Add error checking!
  318. *
  319. *   History:
  320. *       2/15/87    - Initial version
  321. *       2/22/87    - Cleaned up
  322. *       2/22/87    - Documentation
  323. */
  324. void FAR PASCAL StopPlay()
  325. {
  326.     /* Only unhook if currently hooked */
  327.     if (bPlay)
  328.        UnhookWindowsHook( WH_JOURNALPLAYBACK, lpprocPlay );
  329.     /* Let the world know that hook is off */
  330.     bPlay = FALSE;
  331.     return;
  332. }
  333. /*
  334. */
  335.  
  336. /*  Record() - Copy system event messages to a file for later playback.
  337. *
  338. *   Programmer:
  339. *       Eric Fogelin
  340. *       Microsoft Technical Support
  341. *
  342. *   Purpose:
  343. *       WH_JOURNALRECORD call-back hook function used to record messages.
  344. *
  345. *   Arguments:
  346. *       nCode  - Hook code
  347. *       wParam - Not used
  348. *       lParam - Long pointer to a system message, LPSYSMSG.
  349. *
  350. *   Return Value:
  351. *       DWORD value depends on Hook Code.
  352. *
  353. *   Globals (modified):
  354. *       lpTmp            - Far pointer used to get DS for movedata().
  355. *       bufMsg           - Temporary message storage.
  356. *       hFile            - Handle to write-only file.
  357. *
  358. *   Globals (referenced):
  359. *       dwBaseTime       - Reference system time when recording started.
  360. *                          Used to compute elapsed time for later playback.
  361. *                          Elapsed time is placed in bufMsg.lParam.
  362. *
  363. *   Description:
  364. *       From hook function memo (modified):
  365. *       "The hook code of interest is HC_ACTION.
  366. *       wParam is unused.
  367. *       lParam is a long pointer to a SYSMSG structure (LPSYSMSG)
  368. *           with the current message.
  369. *
  370. *       The SYSMSG structure is identical to that used by the
  371. *       WH_JOURNALPLAYBACK structure.  (see below).
  372. *
  373. *       I use this hook to record a sequence of user-initiated
  374. *       events which I later playback, but there are other
  375. *       uses I imagine.  Since the system time when one records
  376. *       and when one plays back the recording will, in general,
  377. *       be different, I keep a global variable (dwBaseTime) around
  378. *       containing the time at the start of the sequence, and store
  379. *       elapsed times in the lParam of the SYSMSG.  When I playback the
  380. *       sequence, I convert those elapsed times into current
  381. *       times by the inverse operation.
  382. *
  383. *       The return value from HC_ACTION is ignored.  Return 0L."
  384. *
  385. *   Limits:
  386. *       Insufficient file error checking.  See code for comments.
  387. *
  388. *   History:
  389. *       2/15/87    - Initial version
  390. *       2/22/87    - Cleaned up
  391. *       2/22/87    - Documentation
  392. */
  393. DWORD FAR PASCAL Record( nCode, wParam, lParam )
  394. int nCode;
  395. WORD wParam;
  396. LPSYSMSG lParam;
  397. {
  398.     /* If it ain't HC_ACTION, pass it along */
  399.     if (nCode != HC_ACTION )
  400.        return DefHookProc( nCode, wParam, (LONG)lParam, (FARPROC FAR *)&lplpfnNextRecord );
  401.  
  402.     /* Need a long pointer for FP_SEG */
  403.     lpTmp = (char far *)&bufMsg;
  404.     /* Get a local copy of the message */
  405.     movedata( FP_SEG( lParam ), FP_OFF( lParam ),
  406.               FP_SEG( lpTmp ), FP_OFF( lpTmp ),
  407.               sizeof(SYSMSG) );
  408.     /* Compute a delta time from beginning recording.  Message time is
  409.        in milliseconds from system power-on.  WARNING: Wrap not handled.
  410.        Wrapping only a problem if system on continuously for ~50 days. */
  411.     bufMsg.lParam -= dwBaseTime;
  412.  
  413.     /* Reopen write-only file using OFSTRUCT.  Add error checking. */
  414.     hFile = OpenFile( (LPSTR)FILENAME, &of, OF_REOPEN | OF_WRITE );
  415.     /* Seek to the end of the file */
  416.     _llseek( hFile, 0L, 2 );
  417.     /* Write the message to disk */
  418.     _lwrite( hFile, (LPSTR)&bufMsg, sizeof(SYSMSG) );
  419.     /* Don't leave files open long */
  420.     _lclose( hFile );
  421.  
  422.     return 0L;
  423. }
  424. /*
  425. */
  426.  
  427. /*  Play() - Copy system event messages from file to system message queue.
  428. *
  429. *   Programmer:
  430. *       Eric Fogelin
  431. *       Microsoft Technical Support
  432. *
  433. *   Purpose:
  434. *       WH_JOURNALPLAYBACK call-back hook function used to replay messages.
  435. *
  436. *   Arguments:
  437. *       nCode  - Hook code
  438. *       wParam - Not used
  439. *       lParam - See Description section.
  440. *
  441. *   Return Value:
  442. *       DWORD value depends on Hook Code.
  443. *
  444. *   Globals (modified):
  445. *       lpTmp            - Far pointer used to get DS for movedata().
  446. *       bufMsg           - Temporary message storage.
  447. *       hFile            - Handle to read-only file.
  448. *       nMsgCtr          - Message counter specifies current message.
  449. *
  450. *   Globals (referenced):
  451. *       dwBaseTime       - Reference system time when playing started.
  452. *                          Used to compute playback time from elapsed time.
  453. *                          Elapsed time is in bufMsg.lParam.
  454. *       nNumMsg          - Number of total messages to playback.
  455. *
  456. *   Description:
  457. *       From hook function memo (modified):
  458. *       "There are two hook codes of interest:
  459. *     
  460. *       HC_GETNEXT
  461. *       wParam is unused.
  462. *       lParam is a long pointer to a SYSMSG structure (LPSYSMSG)
  463. *           where the hook is to put the current messsage to playback.
  464. *     
  465. *       HC_SKIP
  466. *       wParam is unused.
  467. *       lParam is unused.
  468. *     
  469. *       The SYSMSG (System Queue Event) is like a MSG structure:
  470. *       typedef struct {
  471. *           unsigned message;
  472. *           WORD wParam1;
  473. *           WORD wParam2;
  474. *           DWORD lParam;
  475. *       } SYSMSG;
  476. *       The message is a regular Window message (WM_*), the wParam1
  477. *       is pretty much the same as the wParam for a  regular window
  478. *       message, (e.g. a VK_* for a message of WM_KEYDOWN), and the
  479. *       lParam is the current time (expressed as the number of
  480. *       timer ticks since system generation--see GetTickCount()).
  481. *     
  482. *       The hook is used as follows.  Windows, after this hook
  483. *       is installed, disables hardware input.  It will call this
  484. *       hook in lieu of using the system queue until the hook is removed
  485. *       via UnhookWindowsHook.  Whenever the hook gets a HC_GETNEXT,
  486. *       it should poke a SYSMSG through the long pointer given in the
  487. *       lParam and return the time in timer ticks that needs to
  488. *       elapse before the event is ready to be played.  The lParam
  489. *       field of the SYSMSG should also contain the time (not delta
  490. *       time) to play the event.  If the time to play the event is
  491. *       now, or has passed, this hook should return 0L.  Whenever
  492. *       the hook gets a HC_SKIP, it should fetch the next event to
  493. *       poke through the lParam when it next gets a HC_GETNEXT,
  494. *       i.e. it should prepare, but not do it.
  495. *     
  496. *       NOTES:
  497. *       - The hook may be called mutiple times with HC_GETNEXT
  498. *           before getting a HC_SKIP.  The hook should keep returning
  499. *           the same event through the lParam (with the same
  500. *           time-to-be-played in the lParam of the SYSMSG), and a
  501. *           decreasing elpased-time-until-event until it becomes
  502. *           0L, whereafter it returns 0L.
  503. *       - The first message the hook gets (of these two) can be either
  504. *           HC_SKIP or HC_GETNEXT.  Your initialization  code should
  505. *           be invariant.  (I do my initialization when I set the hook.)
  506. *       - If you want to turn the hook off from within the hook, call
  507. *           UnhookWindowsHook from the HC_SKIP case only.
  508. *       - If you want to play events as fast as possible, you'll find
  509. *           that simply returning 0L for every HC_GETNEXT and setting
  510. *           the play time to the current time won't work.  (You hang).
  511. *           I've had success using the mouse double-click speed +1 as
  512. *           the minimum time delay between events (except where the
  513. *           actual event times are less)."
  514. *
  515. *   Limits:
  516. *       Insufficient file error checking.  See code for comments.
  517. *
  518. *   History:
  519. *       2/15/87    - Initial version
  520. *       2/22/87    - Cleaned up
  521. *       2/22/87    - Documentation
  522. */
  523. DWORD FAR PASCAL Play( nCode, wParam, lParam )
  524. int nCode;
  525. WORD wParam;
  526. LPSYSMSG lParam;
  527. {
  528.     long delta;
  529.  
  530.     switch ( nCode ) {
  531.         case HC_SKIP:
  532.             /* Bump up the message counter */
  533.             nMsgCtr++;
  534.             /* Have all messages been played?  Stop playing, if yes */
  535.             if (nMsgCtr >= nNumMsg)
  536.                StopPlay();
  537.  
  538.             /* Get message ready for next HC_GETNEXT */
  539.             /* Reopen read-only file using OFSTRUCT.  Add error checking. */
  540.             hFile = OpenFile( (LPSTR)FILENAME, &of, OF_REOPEN | OF_READ );
  541.             /* Seek to the next message */
  542.             _llseek( hFile, (long)nMsgCtr*sizeof(SYSMSG), 0 );
  543.             /* Get a copy of the message in a local buffer */
  544.             _lread( hFile, (LPSTR)&bufMsg, sizeof(SYSMSG) );
  545.             /* Don't leave files open long */
  546.             _lclose( hFile );
  547.  
  548.             return 0L;
  549.             break;
  550.  
  551.         case HC_GETNEXT:
  552.             /* Need a long pointer for FP_SEG */
  553.             lpTmp = (char far *)&bufMsg;
  554.             /* Put the local copy of the message into queue.  Note: Do
  555.                not modify local message copy; may be asked repeatedly
  556.                for message before next message is requested! */
  557.             movedata( FP_SEG( lpTmp ), FP_OFF( lpTmp ),
  558.                       FP_SEG( lParam ), FP_OFF( lParam ),
  559.                       sizeof(SYSMSG) );
  560.  
  561.             /* Compute a delta time from beginning playback.  Message time is
  562.                in milliseconds from system power-on.  WARNING: Wrap not
  563.                handled.  Wrapping only a problem if system on continuously
  564.                for ~50 days. */
  565.             lParam->lParam = bufMsg.lParam + dwBaseTime;
  566.  
  567.             /* How long from current time must message be played? */
  568.             delta = lParam->lParam - GetTickCount();
  569.  
  570.             /* Return time in milliseconds unless it has already passed
  571.                (delta less than 0).  If passed, return 0. */
  572.             if (delta > 0)
  573.                 return delta;
  574.             else
  575.                 return 0L;
  576.             break;
  577.  
  578.         default:
  579.             /* If it ain't HC_SKIP or HC_GETNEXT, pass it along */
  580.             return DefHookProc( nCode, wParam, (LONG)lParam, (FARPROC FAR *)&lplpfnNextPlay);
  581.             break;
  582.     }
  583. }
  584. /*
  585. */
  586.