[comp.sources.amiga] v02i026: MonIDCMP sources

dpvc@tut.cc.rochester.edu (Davide P. Cervone) (09/18/87)

#	This is a shell archive.
#	Remove everything above and including the cut line.
#	Then run the rest of the file through sh.
#----cut here-----cut here-----cut here-----cut here----#
#!/bin/sh
# shar:    Shell Archiver
#	Run the following text with /bin/sh to create:
#	MonIDCMP.doc
#	MonIDCMP.c
# This archive created: Fri Aug 28 22:16:05 1987
# By:	 ()
cat << \SHAR_EOF > MonIDCMP.doc
OVERVIEW:

MonIDCMP lets you monitor the IntuiMessages that pass through any IDCMP
window.  It prints the IntuiMessage class, mouse coordinates, qualifier
values, and other useful information when appropriate.  For instance, if
a GADGETDOWN message is reported, the GadgetID, GadgetType, GadgetFlags, and
IntuiText are printed; if the gadget is a PROPGADGET, its Pot and Body values
also are displayed.  This is a useful tool for debugging applications, for
determining exactly what messages arrive and when, and for snooping into the
inner workings of other programs.


HOW TO USE MONIDCMP:

To use MonIDCMP you need to know the title of the window you want to monitor
and the title of the screen where that window can be found.  Usually you can
read the window title from the window itself.  If this is not possible,
then use the command:

    1> MonIDCMP LIST WINDOWS

This will list each of the screens together with their associated windows. 
The titles are enclosed in quotes so that any leading or training spaces are
apparent.

Once you have identified the window and screen you want to use, you can begin
monitoring it by typing MONIDCMP followed by the window name and screen title. 
If either the window title or screen title includes spaces, then you should 
put double quotes (") around it.  If the window is on the Workbench Screen, 
then you don't need to specifiy the screen title.

For example, if you want to monitor the window called MyWindow on the Workbench
Screen, then you should type

    1> MonIDCMP MyWindow "Workbench Screen"

or simply

    1> MonIDCMP MyWindow

To monitor the window called 'Input Window' on the screen 'Test Screen', type:

    1> MonIDCMP "Input Window" "Test Screen"

If MonIDCMP can't find the window or screen that you specified, it will tell
you.  Use the MONIDCMP LIST WINDOWS command to get a list of windows that you
can monitor.  The case of the letters does not matter (upper- and lower-case
are treated as the same).

When MonIDCMP finds the window you specified, it will print its version number 
and a message that tells you that the monitor is installed and how to stop
it.  Once the monitor is installed, any IntuiMessages that arrive in the
monitored window will be displayed in the CLI window.

When you are done using MonIDCMP, click in the CLI window where it is running
and press CTRL-C.  MonIDCMP should tell you that it has stopped monitoring the
window.

If MonIDCMP detects a CLOSEWINDOW IntuiMessage for the monitored window, it
expects the window to close so it stops monitoring the window without your
having to press CTRL-C.  Note that this ONLY occurs when a CLOSWINDOW message
arrives.  If the window is closed for some other reason (e.g., you chose QUIT
from a menu), MonIDCMP won't know about it, and you will have to cancel
MonIDCMP using CTRL-C.


OUTPUT RE-DIRECTION:

MonIDCMP can produce a lot of output which may move too fast for you to see,
or you may want a permanent copy of the IDCMP activity saved for future
reference.  In either of these cases, you can re-direct the output from
MonIDCMP to a file (or to a printer) via the standard CLI command-line
output re-direction.  For example:

    1> MonIDCMP >RAM:WB Workbench

would monitor the Workbench window (on the Workbench Screen) but send the
output to the file RAM:WB.  Output re-direction can also be an important tool
during situations that may cause deadlocks due to layer locking (see CAUTIONS
below).


EVENT MASKS:

Sometimes you are only interested in a particular type of IntuiMessage, for
example, you may only want to know about gadget and mouse events even though
the window is reporting menu picks, size changes and disk events as well.  To
do this you use the MASK option of the MonIDCMP command.  The mask is the
bitwise OR of all the IntuiMessage classes that you want to see.  For
instance, GADGETUP is 0x40, GADGETDOWN is 0x20 and MOUSEBUTTONS is 0x8, so
the mask we would use for our example above would be 0x68.  The mask value
follows the word MASK on the command line and must precede the window name:

    1> MonIDCMP MASK 68 Workbench

The mask value is assumed to be in HEX.  The class codes are listed in the
file intuition/intuition.h (right after the struct IntuiMessage definition).

Note that MonIDCMP does not alter the IDCMPFlags field of the monitored
window, so the messages actually displayed will only be those that are in both 
the MASK and the window's IDCMPFlags field.  MonIDCMP does not call 
ModifyIDCMP() to include message additional classes that you specify in MASK.


DISPLAY FORMATS:

MonIDCMP displays the message class, mouse coordinates and qualifiers (if any)
for each message it receives.  The mouse coordinates are shown in decimal. 
The qualifier field is shown in HEX, with translations of the important 
bits (shift, ALT, CTRL and Amiga keys, key repeat, numeric pad, and button
status; L means Left, R means Right, M means Middle (for future compatibility -
see inputevent.h), and C means CapsLock).

The following classes include additional information:

Gadgets:  The GadgetID, gadget text (if any), GadgetType, and GadgetFlags are
shown (the ID is in decimal, the flags are in HEX).  If the gadget is a
STRGADGET, the data stored in the StringInfo buffer also is printed.  If the
gadget is a PROPGADGET, the Pot and Body values are displayed (in decimal), as
well as the KNOBHIT status, for the gadget's direction(s) of movement.

Menus:  The menu code is displayed and the translated MENUNUM, ITEMNUM and
SUBNUM, as are the menu title, item text (if ITEMTEXT is set) and subitem text 
(if ITEMTEXT is set).  If there were multiple menu-item selections during the
menu pick (i.e., the user clicked the left button while still holding the
right), these data are displayed for each item chosen.

Mouse Buttons:  SELECTUP, SELECTDOWN, MENUUP or MENUDOWN is displayed.

Raw Keys:  The raw key value is shown and whether the key was pressed or 
released.  A simple translation to a keyboard key is performed and displayed. 
Since the RAWKEY events include information for mapping dead keys (that
produce diacritical markings), the previous key and qualifiers (if any) are
displayed as well.

Vanilla Keys:  the ASCII code and character are displayed.


TECHNICAL NOTES:

MonIDCMP works by taking over the monitored window's UserPort message port. 
The message port structure includes (among other things) a pointer to a task
to be signalled when a message arrives and a the signal number to use. 
MonIDCMP substitutes a pointer to itself in the message port's mp_SigTask
field so that it is signalled rather than the monitored process.  When
MonIDCMP receives a signal from the message port, it traverses the list of
messags that has arrived (stored in the mp_MsgList field, which is an Exec
List of Exec Node structures which are at the tops of the IntuiMessages that
have arrived in the UserPort) and prints the contents of the messages.  Once
this is done, it calls Signal() to signal the original task that messages have
arrived in the port.  That task then does normal GetMsg() and ReplyMsg() calls
to get and reply to the messages, and is unaware that we "peeked" at the
messages ahead of it.

New messages can arrive in the UserPort at any time, however, even when there
are already messages there.  In order to prevent us from re-printing messages
that we have already seen, we must mark the messages somehow so that we can
distinguish new messaged from old ones.  To do this MonIDCMP sets the high bit
in the ln_Type field (normally NT_MESSAGE) of the Node structure at the top of
each IntuiMessage as it prints it.  This is kind of Kludgy, but it does not
seem to annoy GetMsg() or ReplyMsg() or Intuition, so it seems a useful
device.  The important point is that it is reset to NT_MESSAGE every time
Intuition posts a new message, so even though Intuition re-uses the same
memory areas for it's messages, our flag bit is reset automatically (this is
not true of the other, un-used fields of the Node structure, so I had to use 
the ln_Type field).

Another method would have been to take over the reply ports for each message
as it came in, and reset the flags ourselves, but this seems needlessly
complicated, and causes some other problems as well.

Once we signal the monitored task that messages are ready, it will read ALL
the messages from the UserPort, including any that arrive while it is
processing the others.  In order to prevent us from missing these messages, we
must run at a higher priority than the monitored process; that way, even if
the monitored process is running (i.e., it is not in a Wait() call), as soon
as a new message arrives, we will pre-empt it and can process the new message
without worrying about the other process taking it before we get the chance to
see it.  MonIDCMP takes care of setting the priority so that it is one higher
than that of the monitored process and resetting it when it is done.

Note that this method of monitoring message ports is not restricted to window
UserPorts and IntuiMessages.  It can be used with any type of message port
provided you know where to look for it and you know the formats of the
messages it will contain.  The UserPort fits both these critera perfectly: 
it is at a fixed position within the window structure (which we can find from
screens, which we can find from IntuitionBase) and it has a well-defined
set of messages that are easy to display.


CAUTIONS:

More than one window in a screen can have the same name.  Currenly, MonIDCMP
will monitor the FIRST window that matches the one you supply (here "first"
means first as it is displayed by the MonIDCMP LIST WINDOWS command). 
MonIDCMP does not provide a method for specifying other windows with the
same name (you would have to close the first one in order to monitor the
second, etc.).

Since MonIDCMP prints the contents of the messages BEFORE the monitored
process can act on them, it is possible for MonIDCMP to cause a deadlock 
situation.  For instance, suppose a program locks the screen's layers, and 
then waits for mouse moves or a mouse button up message.  When MonIDCMP
is signalled by one of these events, it attempts to display messages to that
effect on the screen; but since the layers are locked, it can't do it, so it
waits for the layers to be unlocked.  The program will not unlock the layers,
however, until after it receives the button up message.  This produces a
deadlock, and the two processes hang.  For example, this appears to be what 
happens if you monitor the Workbench window and try to move a disk icon.

To get arround this problem, simply re-direct the MonIDCMP output into a
file.  The locked layers will not inhibit writing to the file, so no deadlock
will occur and processing can continue.  This is the only safe way to monitor
the Workbench.

MonIDCMP relies on the fact that it runs at a higher priority than the process
that it is monitoring.  This assures it that it will pre-empt the monitored
process when new messages arrive, hence it will not be able to miss any
messages.  If the monitored process calls Forbid() or Disable(), however, or 
if it changes its priority, this may cause MonIDCMP to miss whole blocks of
messages, particularly the kind that come in pairs (INACTIVEWINDOW and
ACTIVEWINDOW, MENUUP and MENUPICK, etc).  Be careful when monitoring processes
of this kind.

MonIDCMP can seriously impact program timing (i.e., double-click testing,
PROPGADGET updating, etc).  Programs that use IntuiTicks for timing or that
respond to MOUSEMOVE events will be slowed down considerably.  Color changing 
sliders will react sluggishly, for instance.  Re-directing MonIDCMP output to 
a RAM: file will help.

Note that this time dealy can cause a decrease in the amount of available free 
memory.  If IntuiMessages are not replied to quickly enough, Intuition will 
AllocMem() space for new ones as they arrive, but will not de-allocate them 
until the window to which they were posted is closed.  This will happen when 
lots of MOUSEMOVE events come in and MonIDCMP takes time to print them, for 
instance.  To avoid this problem, use the MASK option to eliminate MOUSEMOVE 
and INTUITICKS messages.


COMPILING AND LINKING MONIDCMP:

I used Lattice C to produce MonIDCMP, but I tried to make it as compatible
with Manx as possible.  I do not own the Manx compiler, however, so I can not
test it out.  I suspect that any adjustments will be minor.

For Lattice C (v3.10) I just do:

    1> LC -L -dLATTICE MonIDCMP

That should do it.


AUTHOR:

Davide P. Cervone
University of Rochester Computing Center        DPVC@UORDBV.BITNET
Taylor Hall                                     dpvc@tut.cc.rochester.EDU
Rochester, New York                             dpvc@ur-tut.UUCP
(716)275-2811

Copyright (c) 1987 by Davide P. Cervone, all rights reserved.
SHAR_EOF
cat << \SHAR_EOF > MonIDCMP.c
/*
 *  MonIDCMP.C  -  Monitor the IDCMP port for any window.    26-May-1987
 *
 *  Copyright (c) 1987 by Davide P. Cervone
 *  You may use this code provided this copyright notice is kept intact.
 */

#include <intuition/intuitionbase.h>
#include <libraries/dos.h>
#include <devices/inputevent.h>
#include <stdio.h>

#define USAGE    "MonIDCMP [MASK <mask>] <WindowTitle> [<ScreenTitle>]\n"

#define INTUITION_REV   0
#define ONE             1L
#define SHOWN_FLAG      0x80        /* flag used to tell if a message has */
                                    /* already been seen by the monitor */

#define SHOW_USAGE      0
#define LIST_WINDOWS    1
#define MONITOR_WINDOW  2

#define ARGMATCH(s,n)      (stricmp(s,argv[n]) == 0)
#define NO_MATCH(s1,s2)    (stricmp(s1,s2) != 0)

extern struct Task *FindTask();
extern LONG AllocSignal();

struct IntuitionBase *IntuitionBase = NULL;
struct Task          *theTask;              /* the monitor task */
struct MsgPort       *thePort;              /* the IDCMP port being viewed */
struct List          *theMsgList;           /* the Message List for thePort */
struct Window        *theWindow = NULL;     /* the IDCMP window */
LONG                 theSignal;             /* IDCMP message signal bit */
LONG                 theMask;               /*  and mask */
BYTE                 oldPri;                /* our old priority */
struct Task          *oldTask = NULL;       /* the monitored task */
int                  NotDone = TRUE;        /* not done monitoring? */
int                  GotSignal = FALSE;     /* did AllocSignal succeed? */
char                 *WindowTitle = "";     /* the name of the IDCMP window */
char                 *ScreenTitle = "Workbench Screen";   /* the Screen Title */
ULONG                EventMask = 0xFFFFFFFF;   /* the IntuiMessage classes */
                                               /* that we want to see */

char *version = "MonIDCMP v1.0 (May 1987)";
char *author  = "Copyright (c) 1987 by Davide P. Cervone";


/*
 *  Ctrl_C()
 *
 *  Do nothing routine for Lattice control-C trapping (we do our own).
 */

#ifdef LATTICE
Ctrl_C()
{
   return(0);
}
#endif


/*
 *  DoExit()
 *
 *  General clean-up-and-exit routine.  If the string 's' is not a null
 *  pointer, then print the message that it points to (it can take up to
 *  three optional arguments).  Close the Intuition library if it is open.
 */

void DoExit(s,x1,x2,x3)
char *s, *x1, *x2, *x3;
{
   LONG status = 0;

   if (s != NULL)
   {
      printf(s,x1,x2,x3);
      printf("\n");
      status = RETURN_ERROR;
   }
   if (IntuitionBase != NULL) CloseLibrary(IntuitionBase);
   exit(status);
}


/*
 *  CheckLibOpen()
 *
 *  General library open routine.  It opens a library and sets a pointer
 *  to it.  It checks that the library was openned successfully.
 */

void CheckLibOpen(lib,name,rev)
APTR *lib;
char *name;
int rev;
{
   extern APTR OpenLibrary();

   if ((*lib = OpenLibrary(name,(LONG)rev)) == NULL)
      DoExit("Can't open %s\n",name);
}


/*
 *  ParseArguments()
 *
 *  Parse the command-line arguments and return a function-code that
 *  tells the main program what to do.  The valid options are:  the
 *  words "LIST WINDOWS", which causes MonIDCMP to print a list of all
 *  the screens and their associated windows; or, a window title followed
 *  by an optional screen title (if none is supplied, "Workbench Screen" 
 *  is assumed).  The window title can be preceeded optionally by the word
 *  "MASK" followed by a HEX mask value that represents the IDCMP classes
 *  that should be reported.  Only those messages that match a bit in the
 *  mask value (and in the windows IDCMPFlags field) will be reported.  
 *  For instance, a mask of 2C will allow all GADGETDOWN, MOUSEBUTTONS and
 *  REFRESHWINDOW messages to be reported.
 *
 *  If the arguments do not match one of these templates, then the USAGE
 *  function is performed.
 */

int ParseArguments(argc,argv)
int argc;
char *argv[];
{
   int function = SHOW_USAGE;
   ULONG eMask;
   
   if (argc >=2 && argc <= 5)
   {
      if (argc > 3 && ARGMATCH("MASK",1))
      {
         if (sscanf(argv[2],"%lx",&eMask) != 1)
            DoExit("Bad mask value - '%s'",argv[2]);
         argc -= 2;
         argv += 2;
         EventMask = eMask;
      }
      if (argc == 3 && ARGMATCH("LIST",1) && ARGMATCH("WINDOWS",2))
      {
         function = LIST_WINDOWS;
      } else {
         if (argc <= 3)
         {
            WindowTitle = argv[1];
            if (argc == 3)
               ScreenTitle = argv[2];
            function = MONITOR_WINDOW;
         }
      }
   }
   return(function);
}


/*
 *  ListWindows()
 *
 *  List all the screens and their associated windows.  The first screen
 *  is found in the IntuitionBase structure; subsequent screens are found
 *  from the NextScreen field of the preceeding screen.  The windows for
 *  each screen are linked in a similar fashion.  Forbid() and Permit()
 *  are used to insure that the IntuitionBase lists won't change while 
 *  we're looking at them.  These should be LockIBase() and UnlockIBase(),
 *  but I don't have the documentation for these, so I can't use them.
 */

void ListWindows()
{
   struct Window       *theWindow;
   struct Screen       *theScreen;

   Forbid();
   for (theScreen = IntuitionBase->FirstScreen; theScreen;
      theScreen = theScreen->NextScreen)
   {
      if (theScreen->DefaultTitle)
         printf("\n'%s' [Screen]\n",theScreen->DefaultTitle);
        else
         printf("\n[No Screen Title]\n");
      for (theWindow = theScreen->FirstWindow; theWindow;
         theWindow = theWindow->NextWindow)
      {
         if (theWindow->Title)
            printf("   '%s'\n",theWindow->Title);
           else
            printf("   [No Window Title]\n");
      }
   }
   Permit();
   printf("\n");
}


/*
 *  FindWindow()
 *
 *  Look through the IntuitionBase pointers to find the screen and
 *  window that the user supplied and report an error if they don't
 *  exist.  The titles are matched with a case-insensative compare 
 *  funtion (stricmp).  Forbid() and Permit() are used to be sure that
 *  the Intuition lists don't change while we're looking.  These should
 *  really be LockIBase() and UnlockIBase(), but I don't have the 
 *  documentation for these, so I can't use them.
 */

void FindWindow()
{
   struct Screen *theScreen;
   
   Forbid();

   for (theScreen = IntuitionBase->FirstScreen;
        theScreen && NO_MATCH(ScreenTitle,theScreen->DefaultTitle);
        theScreen = theScreen->NextScreen);

   if (theScreen)
      for (theWindow = theScreen->FirstWindow;
           theWindow && NO_MATCH(WindowTitle,theWindow->Title);
           theWindow = theWindow->NextWindow);

   Permit();

   if (theScreen == NULL)
      DoExit("Screen '%s' not found",ScreenTitle);
   if (theWindow == NULL)
      DoExit("Window '%s' not found on Screen '%s'",WindowTitle,ScreenTitle);
}


/*
 *  SetupMsgPort()
 *
 *  Here's the trick that makes it all work:  the UserPort of the window is
 *  an Exec message port (MsgPort), which contains (among other things) a
 *  pointer to a task to signal when new messages arrive at the port.
 *  We save the old task pointer and insert a pointer to our own task 
 *  in the message port so that we will be signalled instead of the owner
 *  of the window (later, when we have finished printing the messages, we will 
 *  signal the original process "by hand").
 *
 *  We can't use GetMsg() to look at the IntuiMessages since this would
 *  remove them from the port.  Instead, we look through the mp_MsgList
 *  (an Exec List of Exec Node structures) and extract the pointers to 
 *  the IntuiMessages directly, leaving the message port intact.  When we are
 *  done looking, we Signal() the original task that the messages have
 *  arrived.
 *
 *  Once we signal the other process, it uses GetMsg() to remove the messages
 *  from the list.  If new messages arrive while the original process is 
 *  getting message from the port, it could  remove those messages before we 
 *  get the chance to see them.  To overcome this problem, we must run at a 
 *  higher priority than the monitored task.  That way, when a new message 
 *  arrives and we are signalled, we pre-empt the monitored task (i.e., we 
 *  interupt whatever it is doing), and can look through the message list for 
 *  new messages before we let it go any further with the list.  Since most 
 *  of what we do is Wait() for the signal from the message port, we should 
 *  not interfere with the normal functions of the monitored task.
 *
 *  We don't need to ReplyMsg() any messages, since the monitored process
 *  should be doing this itself.
 */
 
void SetupMsgPort()
{
   Forbid();
   thePort    = theWindow->UserPort;
   theMsgList = &(thePort->mp_MsgList);
   theSignal  = thePort->mp_SigBit;
   theMask    = ONE << theSignal;
   oldTask    = thePort->mp_SigTask;
   thePort->mp_SigTask = theTask;
   Permit();
   
   oldPri = SetTaskPri(theTask,(ULONG)(oldTask->tc_Node.ln_Pri + 1));
}


/*
 *  ResetMsgPort()
 *
 *  Put the window's UserPort back the way it was and reset our own 
 *  priority.  Since it is possible that the window was closed (and its
 *  associated memory freed) without our knowing about it, we only reset
 *  the port variables if they still are what we set them to originally.
 *  This avoids unwanted system crashes.
 */

void ResetMsgPort()
{
   Forbid();
   if (thePort->mp_SigBit == theSignal && thePort->mp_SigTask == theTask)
      thePort->mp_SigTask = oldTask;
   Permit();
   
   SetTaskPri(theTask,(ULONG)oldPri);
}


/*
 *  GetSignal()
 *
 *  Try to allocate the same signal that the UserPort is using (this saves
 *  a lot of trouble when we want to signal the original process).  If the
 *  signal is already in use, we give the user the option of aborting or
 *  re-using the signal (we may be signalled at the wrong times if we do).
 *  GotSignal is used when we want to free the signal later.
 */

void GetSignal()
{
   char c;

   if (AllocSignal(theSignal) != theSignal)
   {
      printf("Signal %ld already in use - continue anyway?  ",theSignal);
      c = getchar();
      if (c != 'y' && c != 'Y')
      {
         ResetMsgPort();
         DoExit("Monitor aborted");
      }
   } else {
      GotSignal = TRUE;
   }
}


/*
 *  These macros are helpful in printing the Qualifier fields of the
 *  IntuiMessage that we received
 */

#define CODE            (Code & ~IECODE_UP_PREFIX)
#define QUAL(x)         (Qualifier & (x))
#define ADDQUAL(n)      s = AddQual(s,n)
#define ADDCHAR(c)      *s++ = c;
#define ADDLETTER(q,c)  if (QUAL(q)) ADDCHAR(c);

/*
 *  These are shorhands for the InputEvent qualifier flags
 */

#define LSHIFT          IEQUALIFIER_LSHIFT
#define RSHIFT          IEQUALIFIER_RSHIFT
#define LOCK            IEQUALIFIER_CAPSLOCK
#define SHIFT           (LSHIFT | RSHIFT | LOCK)
#define CONTROL         IEQUALIFIER_CONTROL
#define LALT            IEQUALIFIER_LALT
#define RALT            IEQUALIFIER_RALT
#define ALT             (LALT | RALT)
#define LAMIGA          IEQUALIFIER_LCOMMAND
#define RAMIGA          IEQUALIFIER_RCOMMAND
#define AMIGAS          (LAMIGA | RAMIGA)
#define NUMPAD          IEQUALIFIER_NUMERICPAD
#define REPEAT          IEQUALIFIER_REPEAT
#define MBUTTON         IEQUALIFIER_MIDBUTTON
#define RBUTTON         IEQUALIFIER_RBUTTON
#define LBUTTON         IEQUALIFIER_LEFTBUTTON
#define ANYBUTTON       (LBUTTON | RBUTTON | MBUTTON)
#define RELMOUSE        IEQUALIFIER_RELATIVEMOUSE

/*
 *  AddQual()
 *
 *  Add a comma and a qualifier name to a string and return a pointer
 *  to the character after the end of the added name.
 */

char *AddQual(s,name)
char *s, *name;
{
   *s++ = ',';
   strcpy(s,name);
   return(s + strlen(s));
}


/*
 *  ShowQual()
 *
 *  Display the type of IntuiMessage that we received together with
 *  the mouse position and qualifier flags (if any).  Buttons, shift,
 *  ALT, and Amiga keys are listed with L for left, R for right, M for
 *  middle (button, for future compatibility), and C for CapsLock.
 *
 *  If the event occured in a window other than the monitored one (but
 *  that uses the same UserPort as the monitored window), the window
 *  name is displayed as well (in square brackets).
 */

void ShowQual(name,theMessage)
char *name;
struct IntuiMessage *theMessage;
{
   USHORT Qualifier = theMessage->Qualifier;
   char qual[85];
   char *s = &qual[0];
   int len = 26;

   printf("%-17s (%03d,%03d)",name,theMessage->MouseX,theMessage->MouseY);
   if (Qualifier)
   {
      printf(" %04X",Qualifier); len += 5;
      if (QUAL(ANYBUTTON))
      {
         ADDQUAL("Button(");
         ADDLETTER(LBUTTON,'L');
         ADDLETTER(RBUTTON,'R');
         ADDLETTER(MBUTTON,'M');
         ADDCHAR(')');
      }
      if (QUAL(REPEAT)) ADDQUAL("Repeat");
      if (QUAL(NUMPAD)) ADDQUAL("NumPad");
      if (QUAL(AMIGAS))
      {
         ADDQUAL("Amiga(");
         ADDLETTER(LAMIGA,'L');
         ADDLETTER(RAMIGA,'R');
         ADDCHAR(')');
      }
      if (QUAL(ALT))
      {
         ADDQUAL("ALT(");
         ADDLETTER(LALT,'L');
         ADDLETTER(RALT,'R');
         ADDCHAR(')');
      }
      if (QUAL(CONTROL)) ADDQUAL("CTRL");
      if (QUAL(SHIFT))
      {
         ADDQUAL("Shift(");
         ADDLETTER(LSHIFT,'L');
         ADDLETTER(RSHIFT,'R');
         ADDLETTER(LOCK,  'C');
         ADDCHAR(')');
      }
      ADDCHAR('\0');
      if (qual[0] != '\0')
      {
         qual[0] = ' ';
         len += strlen(qual);
         if (len > 76)
         {
            printf("\n"); len -= 31;
         }
         printf(qual);
      }
   }
   if (theMessage->IDCMPWindow != theWindow)
   {
      if (len + strlen(theMessage->IDCMPWindow->Title) > 72) printf("\n");
      printf(" [%s]",theMessage->IDCMPWindow->Title);
   }
}


/*
 *  ShowMouse()
 *
 *  Display a mouse button code.  We use the constants as defined in
 *  Intuition.h
 */

void ShowMouse(Code)
int Code;
{
   if (Code == SELECTUP)   printf("\n SELECTUP");
   if (Code == SELECTDOWN) printf("\n SELECTDOWN");
   if (Code == MENUUP)     printf("\n MENUUP");
   if (Code == MENUDOWN)   printf("\n MENUDOWN");
}

/*
 *  Macros for Gadget fields
*/

#define GFLAG(f)    (theGadget->Flags & (f))
#define GTYPEF(t)   ((theGadget->GadgetType & GADGETTYPE) & (t))
#define GTYPE(t)    ((theGadget->GadgetType & ~GADGETTYPE) == (t))


/*
 *  ShowGadget()
 *
 *  Display information about gadget messages:  the GadgetID field,
 *  the GadgetText (if non-NULL), the GadgetType, and the GadgetFlags 
 *  (as a HEX value).  If the gadget is a STRGADGET, the contents of the
 *  StringInfo buffer is displayed.  If the gadget is a PROPGADGET, the
 *  values of the Pot and Body fields of the PropInfo structure are shown
 *  (for the directions that the gadget moves), and the status of the KNOBHIT
 *  flag is printed.
 */

void ShowGadget(theGadget)
struct Gadget *theGadget;
{
   int special = (GTYPE(STRGADGET) || GTYPE(PROPGADGET));
   struct StringInfo *SI = (struct StringInfo *) theGadget->SpecialInfo;
   struct PropInfo   *PI = (struct PropInfo *)   theGadget->SpecialInfo;

   printf("\n ID = %d,",theGadget->GadgetID);
   if (theGadget->GadgetText && theGadget->GadgetText->IText)
      printf(" '%s'",theGadget->GadgetText->IText);
   if (GTYPE(STRGADGET))  printf(" STRGADGET");
   if (GTYPE(BOOLGADGET)) printf(" BOOLGADGET");
   if (GTYPE(PROPGADGET)) printf(" PROPGADGET");
   if (GTYPEF(REQGADGET)) printf("+REQGADGET");
   if (GFLAG(SELECTED))   printf(" (SELECTED)");
   printf(", Flags = %04X",theGadget->Flags);
   if (special)
   {
      printf("\n");
      if (GTYPE(STRGADGET))
      {
         if (SI->Buffer) printf(" Buffer = '%s'",SI->Buffer);
      } else {
         if (PI->Flags & FREEHORIZ)
            printf(" HPot = %5d, HBody = %5d,",PI->HorizPot,PI->HorizBody);
         if (PI->Flags & FREEVERT)
            printf(" VPot = %5d, VBody = %5d,",PI->VertPot,PI->VertBody);
         if (PI->Flags & KNOBHIT)
            printf(" KNOBHIT");
           else
            printf(" Knob not hit");
      }
   }
}


/*
 *  Macros for menu fields
 */
#define ITEXT(p)    (((struct IntuiText *)((p)->ItemFill))->IText)
#define MFLAG(f)    (theMItem->Flags & (f))


/*
 *  ShowMenu()
 *
 *  Display the message's Code field and translate it into the menu number,
 *  the item number and the sub-item number.  Then look through the
 *  window's MenuStrip for each of these items (ItemAddress only gives us the
 *  final address, so we look though the MenuStrip by hand), and print their
 *  menu text (if they are text items).  Finally, if the menu item is checked,
 *  we report that.
 *
 *  Since the Amiga allows multiple menu items to be picked within the same
 *  MENUPICK message, we follow the NextSelect field and print out the
 *  data for each additional menu item that was picked.
 */

void ShowMenu(Code,theWindow)
int Code;
struct Window *theWindow;
{
   int menu,item,sub;
   struct Menu *theMenu;
   struct MenuItem *theMItem;
   extern struct MenuItem *ItemAddress();

   do
   {
      if (Code == MENUNULL)
      {
         printf("\n Code = MENUNULL (NOMENU,NOITEM,NOSUB)");
      } else {
         menu = MENUNUM(Code);
         item = ITEMNUM(Code);
         sub  = SUBNUM(Code);
         printf("\n Code = %4X (%d",Code,menu);
         if (item == NOITEM) printf(",NOITEM"); else printf(",%d",item);
         if (sub == NOSUB) printf(",NOSUB)"); else printf(",%d)",sub);

         for(theMenu = theWindow->MenuStrip; menu; menu--)
            theMenu = theMenu->NextMenu;
         printf("  %s",(theMenu->MenuName)? theMenu->MenuName: "[NONAME]");

         for (theMItem = theMenu->FirstItem; item; item--)
            theMItem = theMItem->NextItem;
         if (MFLAG(ITEMTEXT) && theMItem->ItemFill && ITEXT(theMItem))
            printf(", %s",ITEXT(theMItem)); else printf(", [NONAME]");

         if (sub != NOSUB)
         {
            for (theMItem = theMItem->SubItem; sub; sub--)
               theMItem = theMItem->NextItem;
            if (MFLAG(ITEMTEXT) && theMItem->ItemFill && ITEXT(theMItem))
               printf(", %s",ITEXT(theMItem)); else printf(", [NONAME]");
         }

         if (MFLAG(CHECKED)) printf(" (CHECKED)");
         Code = (ItemAddress(theWindow->MenuStrip,(ULONG)Code))->NextSelect;
      }
   } while (Code != MENUNULL);
}


/*
 *  Cheap translation of RAWKEY key codes to their keyboard equivalents.
 *  I could have used RawKeyConvert, but that requires a console device
 *  to be opened, but I didn't want to bother with that.
 */

char Keys[] =
{
 '`','1','2','3','4','5','6','7','8','9','0','-','=','\\',NULL,'\012',
 'Q','W','E','R','T','Y','U','I','O','P','[',']',NULL,'\001','\002','\003',  
 'A','S','D','F','G','H','J','K','L',';','"',NULL,NULL,'\004','\005','\006',
 NULL,'Z','X','C','V','B','N','M',',','.','/',NULL,'\013','\007','\010','\011'
};

/*
 *  Names for non-printing keys
 */

char *KNames[] =
{
   "UNDEFINED", "SPACE", "BACKSPACE", "TAB", "ENTER", "RETURN", "ESC",
   "DEL", NULL, NULL, NULL, "KeyPad -", NULL, "UP ARROW", "DOWN ARROW", 
   "RIGHT ARROW", "LEFT ARROW", "F1", "F2", "F3", "F4", "F5", "F6", "F7",
   "F8", "F9", "F10", NULL, NULL, NULL, NULL, NULL, "HELP", "LEFT SHIFT",
   "RIGHT SHIFT", "CAPS LOCK", "CTRL", "LEFT ALT", "RIGHT ALT", "LEFT AMIGA",
   "RIGHT AMIGA", "LEFT BUTTON", "RIGHT BUTTON", "MIDDLE BUTTON"
};

/*
 *  Error conditions (just to be complete; I don't even know how to
 *  generate these, so I couldn't test that they worked)
 */

char *KErrs[] =
{
   "LAST WAS BAD", "BUFFER OVERFLOW", "CATASTROPHE", "TEST FAILED",
   "POWERUP START", "POWERUP END", "MOUSE MOVE"
};

#define FIRST_NAME      0x40
#define FIRST_ERROR     0xF9
#define KEYCODEMASK     (~IECODE_UP_PREFIX)


/*
 *  ShowKey()
 *
 *  Display the RAWKEY code and which keyboard key produced it.  The
 *  IntuiMessage IAddress field points to the previous key pressed so that
 *  Dead Keys (ones that produce diacritical markings) can be implemented.
 *  See the Enhancer documentation (pp 65-66) for more information.  I used
 *  trial-and-error to figure out what they were pointing at.  It looks like
 *  additional previous keystrokes are stored farther along, but I don't
 *  know how long they are valid.
 */

void ShowKey(Code,PrevKey)
int Code;
unsigned char *PrevKey;
{
   unsigned char c = Code & KEYCODEMASK;
   unsigned char c1, *s;

   printf("\n Key %02X %s",c,(Code & IECODE_UP_PREFIX)? "UP  ": "DOWN");
   if (c < FIRST_NAME)
   {
      c1 = Keys[c];
      if (c1 > ' ')
      {
         printf(" (%c)",c1);
      } else {
         if (c1 == '\013')
            printf(" (KeyPad .)");
           else
            printf(" (KeyPad %c)",(c1 % 10) + '0');
      }
   } else {
      if (Code >= FIRST_ERROR)
      {
         printf(" (%s)",KErrs[Code-FIRST_ERROR]);
      } else {
         s = KNames[c - FIRST_NAME + 1];
         if (s == NULL) s = KNames[0];
         printf(" (%s)",s);
      }
   }
   if (PrevKey && *PrevKey)
   {
      printf(" Previous = %02X",*PrevKey++);
      if (*PrevKey) printf(" Qualifiers = %02X",*PrevKey);
   }
}


/*
 *  ShowVanilla()
 *
 *  Display the ASCII key that was reported.  Non-printing characters
 *  are shown in a semi-reasonable fashion (I don't really like the
 *  "META" stuff).
 */

void ShowVanilla(Code)
int Code;
{
   unsigned char c = Code & 0x7F;

   printf("\n ASCII = %02X",Code);
   if (c >= ' ' && c < 0x7F)
   {
      printf("  (%c",Code);
      if (Code > 0x7F) printf(" = META-%c",c);
      printf(")");
   } else {
      if (c < ' ')
         printf("  (CTRL-%s%c)",(Code > 0x7F)? "META-" :"",c+'@');
        else
         printf("  (%sDEL)",(Code > 0x7F)? "META-" :"");
   }
}


/*
 *  PrintMessage()
 *
 *  Call the right routines for each message class.  Only those that were
 *  specified in the MASK on the command line (default is all messages) are
 *  shown.  Note that only those messages that are actually POSTED to the
 *  UserPort are received by the monitor, so only those classes that appear
 *  in both the MASK and in the window's IDCMPFlags field are shown.  That is
 *  to say, we do NOT perform a ModifyIDCMP() call to include any classes that
 *  are not in the IDCMPFlags field of the window.
 *
 *  If a CLOSEWINDOW event is received for the monitored window, the monitor
 *  assumes that the window is about to be closed and the UserPort freed so
 *  it stops monitoring the window.  If another window using the same port is
 *  closed, however, the monitor does not shut down.
 */

void PrintMessage(theMessage)
struct IntuiMessage *theMessage;
{
   ULONG  Class = theMessage->Class;
   USHORT Code  = theMessage->Code;

   if (EventMask & Class)
   {
      switch(Class)
      {
         case SIZEVERIFY:
            ShowQual("Size Verify",theMessage);
            break;

         case NEWSIZE:
            ShowQual("New Size",theMessage);
            break;

         case REFRESHWINDOW:
            ShowQual("Refresh Window",theMessage);
            break;

         case MOUSEBUTTONS:
            ShowQual("Mouse Button",theMessage);
            ShowMouse(Code);
            break;
   
         case MOUSEMOVE:
            ShowQual("Mouse move",theMessage);
            break;

         case GADGETDOWN:
            ShowQual("Gadget Down",theMessage);
            ShowGadget(theMessage->IAddress);
            break;

         case GADGETUP:
            ShowQual("Gadget Up",theMessage);
            ShowGadget(theMessage->IAddress);
            break;

         case REQSET:
            ShowQual("Requester Set",theMessage);
            break;

         case MENUPICK:
            ShowQual("Menu Pick",theMessage);
            ShowMenu(Code,theMessage->IDCMPWindow);
            break;

         case CLOSEWINDOW:
            ShowQual("Close Window",theMessage);
            break;

         case RAWKEY:
            ShowQual("Raw Key",theMessage);
            ShowKey(Code,theMessage->IAddress);
            break;

         case REQVERIFY:
            ShowQual("Requester Verify",theMessage);
            break;

         case REQCLEAR:
            ShowQual("Requester Clear",theMessage);
            break;

         case MENUVERIFY:
            ShowQual("Menu Verify",theMessage);
            break;

         case NEWPREFS:
            ShowQual("New Preferences",theMessage);
            break;

         case DISKINSERTED:
            ShowQual("Disk Inserted",theMessage);
            break;

         case DISKREMOVED:
            ShowQual("Disk Removed",theMessage);
            break;

         case WBENCHMESSAGE:
            ShowQual("WorkBench Message",theMessage);
            if (Code == WBENCHOPEN)  printf("\n Code = WBENCHOPEN");
            if (Code == WBENCHCLOSE) printf("\n Code = WBENCHCLOSE");
            break;

         case ACTIVEWINDOW:
            ShowQual("Activate Window",theMessage);
            break;

         case INACTIVEWINDOW:
            ShowQual("Inactivate Window",theMessage);
            break;

         case VANILLAKEY:
            ShowQual("Vanilla Key",theMessage);
            ShowVanilla(Code);
            break;

         case INTUITICKS:
            ShowQual("IntuiTicks",theMessage);
            break;

         default:
            printf("Unknown Class:  %04X",theMessage->Class);
            if (theMessage->IDCMPWindow != theWindow)
               printf("   (%s)",theMessage->IDCMPWindow->Title);
            break;
      }
      printf("\n");
   }
   if (Class == CLOSEWINDOW && theMessage->IDCMPWindow == theWindow)
      NotDone = FALSE;
}


/*
 *  MonitorIDCMP()
 *
 *  This is the main loop for the monitor process.  It calls Wait() to
 *  wait for either a message to arrive in the UserPort or for a CTRL-C
 *  to be pressed.  CTRL-C tells MonIDCMP to stop monitoring and clean things
 *  up.
 *
 *  When a message appears in the UserPort, we look through the message list
 *  structure (an Exec List of Exec Node structures), which contains the
 *  pointers to the IntuiMessages posted to the UserPort by Intuition.
 *  Forbid() and Permit() are used so that the list doesn't change while we're 
 *  looking at it.  These should be LockIBase() and UnlockIBase(), but I don't
 *  have the documentation for these, so I can't use them.
 *
 *  For each message in the list we check to see if we've seen it already
 *  and if not, we print it and mark it as seen.  Since Intuition may post
 *  messages to the UserPort at any time (e.g., after we have signalled the
 *  monitored process that new messages have arrived, but before it has had 
 *  a chance to GetMsg() all of them), we have to know which messages we've
 *  already printed and which are new, so when Intuition signals us we don't
 *  reprint messages that are still in the message list.  To do this, we use
 *  a bit of a kludge:  we alter the message's ln_Type field (which should be
 *  NT_MESSAGE) by setting the high bit.  The monitored process doesn't usually
 *  use this field, and it doesn't seem to annoy GetMsg() or ReplyMsg(), so 
 *  I think it works.  The key is that when Intuition re-uses the message, it 
 *  resets this field (actually, I suspect PutMsg() does this), so we can tell
 *  new messages from old messages.  I tried using the ln_Pri and other un-used
 *  Node fields, but these were not reset, so were not useful for this function.
 *  Take note, however, that this DOES alter the contents of the message, and
 *  may cause some programs to malfucntion.
 *
 *  Another approach would have been to take over the ReplyPort for the
 *  messages and keep track of which ones have come back, but this seemed
 *  needlessly complicated and caused other problems instead,
 *
 *  Once we are through printing the new messages, we Permit() and then
 *  Signal() the monitored process that new messages have arrived.  It then
 *  processes them normally, and calls ReplyMsg() itself.
 *
 *  We rely on the fact that we are running at a higher priority than the
 *  monitored process in order to get ALL the messages, even ones that come
 *  while that process is running (i.e., not in a Wait() call).  If the
 *  monitored process calls Forbid() or changes it's priority, however, we
 *  are likely to miss messages, particularly ones that come in groups
 *  (INACTIVEWINDOW/ACTIVEWINDOW, MENUUP/MENUPICK, etc.).
 *
 *  WARNING:  since we print the contents of the messages BEFORE the 
 *  monitored process gets them, this can cause deadlock situations.  For 
 *  instnace, suppose the monitored process has locked the screen layers and
 *  is waiting for mouse moves or button releases.  When these arrive, MonIDCMP
 *  tries to print them, but the layers are locked, so it waits for them to
 *  become unlocked; but this never happens, since the monitored process is
 *  waiting for MonIDCMP to signal the messages.  This appears to be what 
 *  happens when you monitor the WorkBench window and try to move a disk icon, 
 *  for example.  To solve this problem, you can re-direct the output from 
 *  MonIDCMP to a file rather than to the screen.
 *
 *  Note that this method of monitoring a port is not resticted to the
 *  IDCMP ports.  It can be used on ANY port, provided you know where to 
 *  look for it, and what it's contents are supposed to look like.
 */

void MonitorIDCMP()
{
   struct Node *theNode;
   UBYTE theType;
   
   while (NotDone)
   {
      if (Wait(theMask | SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C)
      {
         printf("Cancelling...");
         NotDone = FALSE;
      } else {
         if (theMsgList->lh_TailPred != (struct Node *) theMsgList)
         {
            Forbid();
            for (theNode = theMsgList->lh_Head; theNode->ln_Succ;
               theNode = theNode->ln_Succ)
            {
               if (((theType = theNode->ln_Type) & SHOWN_FLAG) == 0)
               {
                  if (theType != NT_MESSAGE) printf("Type: 0x%X - ",theType);
                  PrintMessage(theNode);
                  theNode->ln_Type |= SHOWN_FLAG;
               }
            }
            Permit();
         }
         Signal(oldTask,theMask);
      }
   }
}


/*
 *  MonitorWindow()
 *
 *  Get the pointer to the window that the user has specified and set up
 *  the MsgPort so that we are signalled when messages arrive in it.  Once
 *  this is done, identify the program, and start the monitoring.  When
 *  the user presses CTRL-C (or closes the monitored window), we are done, so
 *  we clean up the MsgPort and free the signal, then quit.
 */

void MonitorWindow()
{
   theTask = FindTask(NULL);

   FindWindow();
   #ifdef LATTICE
      onbreak(Ctrl_C);
   #endif
   SetupMsgPort();
   GetSignal();

   printf("%s - IDCMP Monitor Program\n",version);
   printf("Monitor Installed - press CTRL-C to cancel\n");

   MonitorIDCMP();

   ResetMsgPort();
   if (GotSignal) FreeSignal(theSignal);
   printf("Monitor Removed\n");
}


/*
 *  main()
 *
 *  Open Intuition and then parse the command-line options to find out what
 *  action the user wants to do.  Either print the usage information or
 *  call the proper routine.  When we're done, exit with no message.
 */

void main(argc,argv)
int argc;
char *argv[];
{
   CheckLibOpen(&IntuitionBase,"intuition.library",INTUITION_REV);

   switch(ParseArguments(argc,argv))
   {
      case SHOW_USAGE:
         printf("Usage:  %s",USAGE);
         printf("   or   MonIDCMP LIST WINDOWS\n");
         break;

      case LIST_WINDOWS:
         ListWindows();
         break;

      case MONITOR_WINDOW:
         MonitorWindow();
         break;
   }
   DoExit(NULL);
}
SHAR_EOF
#	End of shell archive
exit 0