/*
** $VER: sana2meter.c 1.3 (15 Apr 1996)
**
** (C) Copyright 1996 Marius Gröger
**     All Rights Reserved
**
** $HISTORY:
**
** 15 Apr 1996 : 001.003 :  support for broken gadtools < V40.1
** 14 Apr 1996 : 001.002 :  failing DoIO()'s will turn off further displaying
**                          of the corresponding values
** 10 Apr 1996 : 001.001 :  packet type tracking
** 26 Mar 1996 : 001.000 :  created
*/

#define DEBUG 0
#define USE_EXTREADARGS 1

/*F*/ /* includes */

#include <clib/alib_protos.h>
#include <clib/dos_protos.h>
#include <pragmas/dos_pragmas.h>
#include <clib/utility_protos.h>
#include <pragmas/utility_pragmas.h>
#include <clib/exec_protos.h>
#include <pragmas/exec_sysbase_pragmas.h>
#include <clib/intuition_protos.h>
#include <pragmas/intuition_pragmas.h>
#include <clib/gadtools_protos.h>
#include <pragmas/gadtools_pragmas.h>
#include <clib/icon_protos.h>
#include <pragmas/icon_pragmas.h>
#include <clib/locale_protos.h>
#include <pragmas/locale_pragmas.h>

#include <gtlayout/gtlayout.h>

#include <exec/libraries.h>
#include <exec/devices.h>
#include <exec/memory.h>
#include <exec/io.h>

#include <workbench/startup.h>

#include <utility/tagitem.h>

#include <netinclude:devices/sana2.h>

#define AsmPools
#include <pools/pool_lib.h>

#if (USE_EXTREADARGS != 0)
#include <extrdargs.h>
#endif

#include "compiler.h"
#include "debug.h"

#define CATCOMP_NUMBERS
#include "locale.h"

#include <strings.h>
#include <stddef.h>
/*E*/
/*F*/ /* revision */
#define VERSION 37
#define REVISION 3
#define DATE "15.04.96"
#define VERS "sana2meter 37.3"
#define VSTRING "sana2meter 37.3 (15.04.96)"
#define VERSTAG "\0$VER: sana2meter 37.3 (15.04.96)"
PRIVATE const UBYTE version[]=VERSTAG;
/*E*/

/*F*/ /* watches */
struct TrackType
{
   ULONG                         tt_Type;
   UBYTE                         tt_Label[30];
   UBYTE                         tt_Key[12];
   ULONG                         tt_Flags;
   struct Sana2PacketTypeStats   tt_Stats;
};
#define TTF_ACTIVE            (1<<0)
#define TTF_TRACKED           (1<<1)
#define TTF_INITIAL_OPACKETS  (1<<2)
#define TTF_INITIAL_IPACKETS  (1<<3)
#define TTF_INITIAL_OBYTES    (1<<4)
#define TTF_INITIAL_IBYTES    (1<<5)
#define TTF_INITIAL_DPACKETS  (1<<6)
#define TTF_INITIAL_SETUP     (1<<7)

PRIVATE const ULONG ttmaskflags[] =
{
   TTF_INITIAL_OPACKETS,
   TTF_INITIAL_IPACKETS,
   TTF_INITIAL_OBYTES,
   TTF_INITIAL_IBYTES,
   TTF_INITIAL_DPACKETS
};

/***************************************************************************/


enum Member
{
   MEMBER_NONE,
   MEMBER_DEVICEQUERY,
   MEMBER_GLOBALSTATS,
   MEMBER_SPECIALSTATS,
   MEMBER_TRACKTYPE
};


struct NewWatch
{
   enum Member    nw_Member;        /* classification */
   STRPTR         nw_Key;           /* option keyword */
   LONG           nw_LabelID;       /* locale ID */
   ULONG          nw_StructOffset;
   ULONG          nw_Flags;         /* see WF_#? below */
};

struct Watch
{
   struct MinNode          w_Link;
   enum Member             w_Member;      /* copy of NewWatch */
   STRPTR                  w_Key;         /* copy of NewWatch */
   struct TrackType     *  w_TrackType;   /* only if (w_Member == MEMBER_TRACKTYPE) */
   STRPTR                  w_Label;       /* translated localeid */
   ULONG                   w_Flags;       /* copy of NewWatch */
   ULONG                   w_ID;          /* for GUI */
   ULONG                *  w_Value;       /* pointer to monitored value */
};
#define WF_ACTIVE   (1<<0)
#define WF_STATIC   (1<<1)
#define WF_FIRSTTT  (1<<2)
#define WF_NOLABEL  (1<<3)
#define WF_OFF      (1<<4)

#define SCANWATCHES(w, list) for(w = (struct Watch*)((list)->mlh_Head); w->w_Link.mln_Succ; w=(struct Watch*)w->w_Link.mln_Succ)


/***************************************************************************/


#define OFF_S2DQ(member) offsetof(struct Sana2DeviceQuery, member)
#define OFF_S2DS(member) offsetof(struct Sana2DeviceStats, member)
#define OFF_S2PTS(member) offsetof(struct Sana2PacketTypeStats, member)

PRIVATE const struct NewWatch ttwatches[] =
{
 { MEMBER_TRACKTYPE,   NULL,         TXT_TTOPACKETS, OFF_S2PTS(PacketsSent),         WF_ACTIVE             },
 { MEMBER_TRACKTYPE,   NULL,         TXT_TTIPACKETS, OFF_S2PTS(PacketsReceived),     WF_ACTIVE             },
 { MEMBER_TRACKTYPE,   NULL,         TXT_TTOBYTES,   OFF_S2PTS(BytesSent),           WF_ACTIVE             },
 { MEMBER_TRACKTYPE,   NULL,         TXT_TTIBYTES,   OFF_S2PTS(BytesReceived),       WF_ACTIVE             },
 { MEMBER_TRACKTYPE,   NULL,         TXT_TTDPACKETS, OFF_S2PTS(PacketsDropped),      WF_ACTIVE             },
 { MEMBER_NONE }
};
#define NUM_TTWATCHES ((sizeof(ttwatches)/sizeof(struct NewWatch) - 1))

PRIVATE const struct NewWatch stdwatches[] =
{
 { MEMBER_GLOBALSTATS,  "opackets",  TXT_OPACKETS,   OFF_S2DS(PacketsSent),          WF_ACTIVE             },
 { MEMBER_GLOBALSTATS,  "ipackets",  TXT_IPACKETS,   OFF_S2DS(PacketsReceived),      WF_ACTIVE             },
 { MEMBER_GLOBALSTATS,  "baddata",   TXT_BADDATA,    OFF_S2DS(BadData),              WF_ACTIVE             },
 { MEMBER_GLOBALSTATS,  "overruns",  TXT_OVERRUNS,   OFF_S2DS(Overruns),             WF_ACTIVE             },
 { MEMBER_GLOBALSTATS,  "unknown",   TXT_UNKNOWN,    OFF_S2DS(UnknownTypesReceived), WF_ACTIVE             },
 { MEMBER_GLOBALSTATS,  "reconfigs", TXT_RECONFIGS,  OFF_S2DS(Reconfigurations),     0                     },
 { MEMBER_DEVICEQUERY,  "mtu",       TXT_MTU,        OFF_S2DQ(MTU),                  WF_ACTIVE | WF_STATIC },
 { MEMBER_DEVICEQUERY,  "bps",       TXT_BPS,        OFF_S2DQ(BPS),                  WF_ACTIVE | WF_STATIC },
 { MEMBER_DEVICEQUERY,  "hwtype",    TXT_HWTYPE,     OFF_S2DQ(HardwareType),         WF_ACTIVE | WF_STATIC },
 { MEMBER_SPECIALSTATS, "special1",  0,              0,                              WF_NOLABEL            },
 { MEMBER_SPECIALSTATS, "special2",  0,              0,                              WF_NOLABEL            },
 { MEMBER_SPECIALSTATS, "special3",  0,              0,                              WF_NOLABEL            },
 { MEMBER_SPECIALSTATS, "special4",  0,              0,                              WF_NOLABEL            },
 { MEMBER_SPECIALSTATS, "special5",  0,              0,                              WF_NOLABEL            },
 { MEMBER_SPECIALSTATS, "special6",  0,              0,                              WF_NOLABEL            },
 { MEMBER_SPECIALSTATS, "special7",  0,              0,                              WF_NOLABEL            },
 { MEMBER_SPECIALSTATS, "special8",  0,              0,                              WF_NOLABEL            },
 { MEMBER_SPECIALSTATS, "special9",  0,              0,                              WF_NOLABEL            },
 { MEMBER_SPECIALSTATS, "special10", 0,              0,                              WF_NOLABEL            },
 { MEMBER_SPECIALSTATS, "special11", 0,              0,                              WF_NOLABEL            },
 { MEMBER_SPECIALSTATS, "special12", 0,              0,                              WF_NOLABEL            },
 { MEMBER_SPECIALSTATS, "special13", 0,              0,                              WF_NOLABEL            },
 { MEMBER_SPECIALSTATS, "special14", 0,              0,                              WF_NOLABEL            },
 { MEMBER_SPECIALSTATS, "special15", 0,              0,                              WF_NOLABEL            },
 { MEMBER_SPECIALSTATS, "special16", 0,              0,                              WF_NOLABEL            },
 { MEMBER_SPECIALSTATS, "special17", 0,              0,                              WF_NOLABEL            },
 { MEMBER_SPECIALSTATS, "special18", 0,              0,                              WF_NOLABEL            },
 { MEMBER_SPECIALSTATS, "special19", 0,              0,                              WF_NOLABEL            },
 { MEMBER_SPECIALSTATS, "special20", 0,              0,                              WF_NOLABEL            },
 { MEMBER_NONE }
};

#define NUM_STDWATCHES ((sizeof(stdwatches)/sizeof(struct NewWatch) - 1))
/*E*/
/*F*/ /* intervals */
PRIVATE const ULONG intervals[] =
{
   {  1 },
   {  2 },
   {  3 },
   {  4 },
   {  5 },
   { 10 },
   { 20 },
   { 30 },
   { 40 },
   { 50 },
   { 60 }
};
#define NUM_REFRESHS (sizeof(intervals)/sizeof(ULONG))
/*E*/
/*F*/ /* menu */
PRIVATE const struct NewMenu menu[] =
{
   NM_TITLE, (STRPTR)MENU_PROJECT,          NULL, 0,                      0, NULL,
   NM_ITEM,  (STRPTR)MENU_SAVECONFIG,       NULL, 0,                      0, NULL,
   NM_ITEM,  (STRPTR)NM_BARLABEL,           NULL, 0,                      0, NULL,
   NM_ITEM,  (STRPTR)MENU_ABOUT,            NULL, 0,                      0, NULL,
   NM_ITEM,  (STRPTR)NM_BARLABEL,           NULL, 0,                      0, NULL,
   NM_ITEM,  (STRPTR)MENU_QUIT,             NULL, 0,                      0, NULL,
   NM_TITLE, (STRPTR)MENU_SETTINGS,         NULL, 0,                      0, NULL,
   NM_ITEM,  (STRPTR)MENU_UPDATE,           NULL, 0,                      0, NULL,
   NM_SUB,   (STRPTR)MENU_REFRESH1,         NULL, CHECKIT,               ~1, NULL,
   NM_SUB,   (STRPTR)MENU_REFRESH2,         NULL, CHECKIT,               ~2, NULL,
   NM_SUB,   (STRPTR)MENU_REFRESH3,         NULL, CHECKIT,               ~4, NULL,
   NM_SUB,   (STRPTR)MENU_REFRESH4,         NULL, CHECKIT,               ~8, NULL,
   NM_SUB,   (STRPTR)MENU_REFRESH5,         NULL, CHECKIT,              ~16, NULL,
   NM_SUB,   (STRPTR)MENU_REFRESH10,        NULL, CHECKIT,              ~32, NULL,
   NM_SUB,   (STRPTR)MENU_REFRESH20,        NULL, CHECKIT,              ~64, NULL,
   NM_SUB,   (STRPTR)MENU_REFRESH30,        NULL, CHECKIT,             ~128, NULL,
   NM_SUB,   (STRPTR)MENU_REFRESH40,        NULL, CHECKIT,             ~256, NULL,
   NM_SUB,   (STRPTR)MENU_REFRESH50,        NULL, CHECKIT,             ~512, NULL,
   NM_SUB,   (STRPTR)MENU_REFRESH60,        NULL, CHECKIT,            ~1024, NULL,
   NM_TITLE, (STRPTR)MENU_SHOW,             NULL, 0,                      0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_ITEM,  NULL,                          NULL, CHECKIT|MENUTOGGLE,     0, NULL,
   NM_END
};

#define FIRST_REFRESH_ITEM 8

#define FIRST_WATCH_ITEM  20

/*E*/
/*F*/ /* global definitions */
PRIVATE const UBYTE TEMPLATE[] = "S=SHOW/K,"              \
                                 "TT=TRACKTYPE/K,"        \
                                 "DEVICE,"                \
                                 "U=UNIT/K/N,"            \
                                 "FORCE/S,"               \
                                 "RF=REFRESH/K/N,"        \
                                 "L=LIST/S,"              \
                                 "FONT=FONTNAME/K,"       \
                                 "FONTSIZE/K/N,"          \
                                 "TOP/K/N,"               \
                                 "LEFT/K/N,"              \
                                 "SCREEN=PUBSCREEN/K,"    \
                                 "NA=NOACTIVATE/S,"       \
                                 "W2B=WINDOWTOBACK/S,"    \
                                 "WINDOW/K";

enum
{
   ARG_SHOW,
   ARG_TRACKTYPE,
   ARG_DEVICE,
   ARG_UNIT,
   ARG_FORCE,
   ARG_REFRESH,
   ARG_LIST,
   ARG_FONT,
   ARG_FONTSIZE,
   ARG_TOP,
   ARG_LEFT,
   ARG_PUBSCREEN,
   ARG_NOACTIVATE,
   ARG_WINDOWTOBACK,
   ARG_WINDOW,
   ARG_COUNT
};

#define DEF_DEVICE   "devs:networks/magplip.device"
#define DEF_UNIT 0
#define DEF_REFRESH 2
#define DEF_FONTSIZE 8
#define DEF_LEFT 30
#define DEF_TOP 20
PRIVATE const UBYTE DEF_CONSOLE[] = "CON:0/0/400/200/SANA-II Meter Tool Output/AUTO/WAIT/CLOSE";
PRIVATE const UBYTE EMAIL_AUTHOR[] = "mag@sysgo.de";

#define POOLKIND     (MEMF_ANY|MEMF_CLEAR)
#define POOLPUDDLE   (4*1024)
#define POOLTHRESH   (3*1024)

PRIVATE UBYTE KEYDELIMITERS[]   = ",;| ";
PRIVATE UBYTE TTMASKSEPARATOR[] = ":/";
PRIVATE UBYTE TTMASKACTIVE[]    = "+xX#";
PRIVATE UBYTE TTMASKINACTIVE[]  = "-oO.";

#define NUM_SPECIALSTATS 10

   /* Global data structures that will be passed to other system components */
typedef struct PublicData
{
   struct IOSana2Req                pd_S2Req;
   struct timerequest               pd_TReq;
   struct Sana2DeviceQuery          pd_S2DeviceQuery;
   struct Sana2DeviceStats          pd_S2DeviceStats;
   struct Sana2SpecialStatHeader    pd_S2SpecialStats;
   struct Sana2SpecialStatRecord    pd_S2SpecialStatRecs[NUM_SPECIALSTATS];
   struct TrackType              *  pd_TrackedTypes;
   struct TTextAttr                 pd_Font;
   UBYTE                            pd_Fontname[32];
   UBYTE                            pd_Pubscreen[MAXPUBSCREENNAME];
   UBYTE                            pd_Console[100];
} *PD;

   /* Global data structures that will be used locally only */
typedef struct GlobalData
{
   struct Library                *  gd_SysBase,
                                 *  gd_UtilityBase,
                                 *  gd_GadToolsBase,
                                 *  gd_GTLayoutBase,
                                 *  gd_IntuitionBase,
                                 *  gd_IconBase,
                                 *  gd_DOSBase;
   ULONG                            gd_argc;
   UBYTE                         ** gd_argv;
   struct LocaleInfo                gd_LocaleInfo;
   struct MsgPort                *  gd_Port;
   struct timeval                   gd_time;
   UBYTE                            gd_ProgramName[32];
   UBYTE                            gd_Device[100];
   UBYTE                            gd_WindowTitle[100];
   LONG                             gd_Unit;
   ULONG                            gd_Flags;
   ULONG                            gd_Refresh;
   struct Task                   *  gd_We;
   int                              gd_GetGlobalStats    : 1;
   int                              gd_GetSpecialStats   : 1;
   int                              gd_GetTrackType      : 1;
   int                              gd_CustomFont        : 1;
   int                              gd_List              : 1;
   int                              gd_CLI               : 1;
   int                              gd_InputSelected     : 1;
   int                              gd_OutputSelected    : 1;
   int                              gd_CTSelected        : 1;
   int                              gd_Force             : 1;
   int                              gd_Inactive          : 1;
   int                              gd_ToBack            : 1;
   struct LayoutHandle           *  gd_Handle;
   struct NewMenu                *  gd_NewMenu;
   struct Menu                   *  gd_Menu;
   LONG                             gd_Left;
   LONG                             gd_Top;
   LONG                             gd_ValidSpecialStats;
   PD                               gd_Public;
   void                          *  gd_Pool;
   BPTR                             gd_Input;
   BPTR                             gd_Output;
   struct MsgPort                *  gd_OldConsoleTask;
   BPTR                             gd_OldInput;
   BPTR                             gd_OldOutput;
   struct MinList                   gd_Watches;
   struct Watch                  *  gd_StdWatches;
   ULONG                            gd_NumTT;
   struct Watch                  *  gd_TrackWatches;
} *GD;

#ifdef __SASC
     /*
     ** redirect all shared library bases to our device base.
     */
#  define SysBase       gd->gd_SysBase
#  define GadToolsBase  gd->gd_GadToolsBase
#  define GTLayoutBase  gd->gd_GTLayoutBase
#  define DOSBase       gd->gd_DOSBase
#  define UtilityBase   gd->gd_UtilityBase
#  define LocaleBase    gd->gd_LocaleInfo.li_LocaleBase
#  define IconBase      gd->gd_IconBase
#  define IntuitionBase gd->gd_IntuitionBase
     /*
     ** This macro declares a local variable which temporary gets
     ** SysBase directly from AbsExecBase.
     */
#  define LOCALSYSBASE struct { void *gd_SysBase; } *gd = (void*)0x4
#else
#  error Please define library bases for your compiler
#endif

/*E*/

/*F*/ /* private symbols */
PRIVATE VOID message(GD gd, LONG, LONG, LONG, ...);
PRIVATE BOOL parseargs(GD gd);
PRIVATE VOID cleanup(GD gd);
PRIVATE BOOL opendevs(GD gd);
PRIVATE GD createenv(ULONG argc, UBYTE **argv);
PRIVATE BOOL preparemenu(GD gd);
PRIVATE VOID checkmenu(GD gd);
PRIVATE VOID getspeciallabels(GD gd);
PRIVATE BOOL preparewatches(GD gd);
PRIVATE VOID updateupdate(GD gd);
PRIVATE UWORD updateglobalstats(GD gd);
PRIVATE UWORD updatespecialstats(GD gd);
PRIVATE UWORD updatetracktype(GD gd);
PRIVATE VOID updategui(GD gd, BOOL complete);
PRIVATE VOID closegui(GD gd);
PRIVATE BOOL opengui(GD gd);
PRIVATE VOID *allocpvec(GD gd, ULONG len);
PRIVATE VOID freepvec(GD gd, VOID *mem);
/*E*/
/*F*/ /* exported symbols */
PUBLIC VOID SAVEDS STDARGS main(ULONG argc, UBYTE **argv);
/*E*/
/*F*/ /* imported symbols */
IMPORT STDARGS STRPTR GetString(struct LocaleInfo *li, LONG stringNum);
IMPORT STDARGS VOID exit(LONG);
IMPORT STDARGS VOID sprintf(char *, char *, ...);
/*E*/

/*F*/ PRIVATE BOOL preparemenu(GD gd)
{
   struct NewMenu *nm;
   struct Watch *w;
   ULONG i, ss;
   BOOL rc = TRUE;

   if (gd->gd_NewMenu = allocpvec(gd, sizeof(menu)+gd->gd_NumTT*(1+NUM_TTWATCHES)*sizeof(struct NewMenu)))
   {
      CopyMem((void*)menu, (void*)gd->gd_NewMenu, sizeof(menu));

      for (nm = gd->gd_NewMenu; nm->nm_Type != NM_END; ++nm)
      {
         STRPTR p;

         if ((nm->nm_Label != NM_BARLABEL) && !(menu->nm_Type & NM_IGNORE))
         {
            p = GetString(&gd->gd_LocaleInfo, (LONG)nm->nm_Label);
            if (p[1] == '\0')
            {
               nm->nm_CommKey = p;
               nm->nm_Label = p+2;
            }
            else
               nm->nm_Label = p;
         }
      }

      ss = gd->gd_ValidSpecialStats;
      for (i=0, nm = gd->gd_NewMenu + FIRST_WATCH_ITEM; i<NUM_STDWATCHES; ++nm, ++i)
      {
         w = gd->gd_StdWatches + i;
         nm->nm_UserData = (APTR)w;
         nm->nm_Label = w->w_Label;
         if (w->w_Member == MEMBER_SPECIALSTATS)
         {
            if (!ss) break;
            if (!--ss)
            {
               ++nm;
               break;
            }
         }
      }

      for (w=gd->gd_TrackWatches; w->w_Link.mln_Succ; w=(struct Watch*)w->w_Link.mln_Succ, ++nm)
      {
         if (w->w_Flags & WF_FIRSTTT)
         {
            nm->nm_Type = NM_ITEM;
            nm->nm_Label = w->w_TrackType->tt_Label;
            nm->nm_CommKey = NULL;
            nm->nm_Flags = 0;
            nm->nm_UserData = NULL;
            ++nm;
         }
         nm->nm_Type = NM_SUB;
         nm->nm_Label = w->w_Label;
         nm->nm_CommKey = NULL;
         nm->nm_Flags = CHECKIT|MENUTOGGLE;
         nm->nm_UserData = (APTR)w;
      }
      nm->nm_Type = NM_END;
   }
   else rc = TRUE;

   return rc;
}
/*E*/
/*F*/ PRIVATE BOOL preparewatches(GD gd)
{
   PD pd = gd->gd_Public;
   const struct NewWatch * nw;
   struct Watch *w;
   struct Sana2SpecialStatRecord *sr = pd->pd_S2SpecialStatRecs;
   BOOL rc = TRUE;

   NewList((struct List*)&gd->gd_Watches);

   if (gd->gd_StdWatches = allocpvec(gd, NUM_STDWATCHES*sizeof(struct Watch)))
   {
      for (w = gd->gd_StdWatches, nw = stdwatches; nw->nw_Member != MEMBER_NONE; ++nw, ++w)
      {
         w->w_Member = nw->nw_Member;
         w->w_Key = nw->nw_Key;
         if (!(nw->nw_Flags & WF_NOLABEL))
            w->w_Label = GetString(&gd->gd_LocaleInfo, nw->nw_LabelID);
         w->w_Flags = nw->nw_Flags;

         if (nw->nw_Member == MEMBER_GLOBALSTATS)
            w->w_Value = (ULONG*)((UBYTE*)&pd->pd_S2DeviceStats + nw->nw_StructOffset);
         else if (nw->nw_Member == MEMBER_DEVICEQUERY)
            w->w_Value = (ULONG*)((UBYTE*)&pd->pd_S2DeviceQuery + nw->nw_StructOffset);
         else if (nw->nw_Member == MEMBER_SPECIALSTATS)
         {
            w->w_Value = &sr->Count;
            ++sr;
         }
         else
            w->w_Value = NULL;

         AddTail((struct List*)&gd->gd_Watches, (struct Node*)&w->w_Link);
      }
   } else rc = FALSE;

   return rc;
}
/*E*/
/*F*/ PRIVATE BOOL preparetts(GD gd)
{
   PD pd = gd->gd_Public;
   struct NewWatch *nw;
   struct TrackType *tt;
   struct Watch *w;
   BOOL rc = TRUE, first;
   ULONG numtt, i;

   if (gd->gd_TrackWatches = allocpvec(gd, NUM_TTWATCHES*gd->gd_NumTT*sizeof(struct Watch)))
   {
      w = gd->gd_TrackWatches;
      for (tt=pd->pd_TrackedTypes, numtt = gd->gd_NumTT; numtt; ++tt,  --numtt)
      {
         for (i=0, first=TRUE, nw=(struct NewWatch *)ttwatches; nw->nw_Member != MEMBER_NONE;
                                                                     ++i, ++nw, first=FALSE)
         {
            w->w_Member = nw->nw_Member;
            w->w_TrackType = tt;
            w->w_Key = first ? tt->tt_Key : NULL;
            w->w_Label = GetString(&gd->gd_LocaleInfo, nw->nw_LabelID);
            w->w_Flags = nw->nw_Flags | (first ? WF_FIRSTTT : 0);
            if (tt->tt_Flags & TTF_INITIAL_SETUP)
            {
               if (tt->tt_Flags & ttmaskflags[i])
                  w->w_Flags |= WF_ACTIVE;
               else
                  w->w_Flags &= ~WF_ACTIVE;
            }
            w->w_Value = (ULONG*)((UBYTE*)&tt->tt_Stats + nw->nw_StructOffset);
            AddTail((struct List*)&gd->gd_Watches, (struct Node*)&w->w_Link);

            ++w;
         }
      }
   }

   return rc;
}
/*E*/
/*F*/PRIVATE VOID checkmenu(GD gd)
{
   struct NewMenu *nm;
   struct Watch *w;
   int i;

   for (i=0, nm = gd->gd_NewMenu + FIRST_REFRESH_ITEM; i<NUM_REFRESHS; ++nm, ++i)
   {
      if (intervals[i] == gd->gd_Refresh)
         nm->nm_Flags |= CHECKED;
      else
         nm->nm_Flags &= ~CHECKED;
   }

   for (i=NUM_STDWATCHES+gd->gd_NumTT*(NUM_TTWATCHES+1), nm = gd->gd_NewMenu + FIRST_WATCH_ITEM; i; ++nm, --i)
   {
      if (w = (struct Watch*)nm->nm_UserData)
      {
         if (w->w_Flags & WF_OFF)
            nm->nm_Flags |= NM_ITEMDISABLED;

         if (w->w_Flags & WF_ACTIVE)
            nm->nm_Flags |= CHECKED;
         else
            nm->nm_Flags &= ~CHECKED;
      }
   }
   return ;
}
/*E*/
/*F*/ PRIVATE VOID getspeciallabels(GD gd)
{
   PD pd = gd->gd_Public;
   struct Watch *w;
   int i;
   BOOL specials = FALSE;

   SCANWATCHES(w, &gd->gd_Watches)
   {
      if (!specials)
      {
         if (w->w_Member == MEMBER_SPECIALSTATS)
         {
            specials = TRUE;
            i = 0;
         }
      }
      if (specials)
      {
         if (i >= NUM_SPECIALSTATS || !(pd->pd_S2SpecialStatRecs[i].String))
            break;
         w->w_Label = pd->pd_S2SpecialStatRecs[i].String;
         ++i;
      }
   }

   gd->gd_ValidSpecialStats = i;

   return ;
}
/*E*/

/*F*/ PRIVATE VOID updateupdate(GD gd)
{
   PD pd = gd->gd_Public;
   struct Watch *w;
   struct TrackType *tt;
   ULONG i;

   gd->gd_GetGlobalStats = 0;
   gd->gd_GetSpecialStats = 0;
   gd->gd_GetTrackType = 0;

   for(i=gd->gd_NumTT, tt=pd->pd_TrackedTypes; i; --i, ++tt)
      tt->tt_Flags &= ~TTF_ACTIVE;

   SCANWATCHES(w, &gd->gd_Watches)
   {
      if ((w->w_Flags & WF_ACTIVE) && (w->w_Member == MEMBER_GLOBALSTATS))
         gd->gd_GetGlobalStats = 1;

      if ((w->w_Flags & WF_ACTIVE) && (w->w_Member == MEMBER_SPECIALSTATS))
         gd->gd_GetSpecialStats = 1;

      if (w->w_Member == MEMBER_TRACKTYPE)
      {
         if (w->w_Flags & WF_ACTIVE)
         {
            w->w_TrackType->tt_Flags |= TTF_ACTIVE;
            gd->gd_GetTrackType = 1;
         }
      }
   }
   return ;
}
/*E*/
/*F*/ PRIVATE UWORD updateglobalstats(GD gd)
{
   PD pd = gd->gd_Public;
   UWORD rc = 0;

   pd->pd_S2Req.ios2_StatData = (void*)&pd->pd_S2DeviceStats;
   pd->pd_S2Req.ios2_Req.io_Command = S2_GETGLOBALSTATS;
   if (DoIO((struct IORequest*)&pd->pd_S2Req))
   {
      struct Watch *w;

      SCANWATCHES(w, &gd->gd_Watches)
      {
         if (w->w_Member == MEMBER_GLOBALSTATS)
            w->w_Flags = (w->w_Flags & ~WF_ACTIVE) | WF_OFF;
      }
      rc = 1;
   }

   return rc;
}
/*E*/
/*F*/ PRIVATE UWORD updatespecialstats(GD gd)
{
   struct PublicData *pd = gd->gd_Public;
   UWORD rc = 0;

   pd->pd_S2SpecialStats.RecordCountMax = NUM_SPECIALSTATS;
   pd->pd_S2Req.ios2_StatData = (void*)&pd->pd_S2SpecialStats;
   pd->pd_S2Req.ios2_Req.io_Command = S2_GETSPECIALSTATS;
   if (DoIO((struct IORequest*)&pd->pd_S2Req))
   {
      struct Watch *w;

      SCANWATCHES(w, &gd->gd_Watches)
      {
         if (w->w_Member == MEMBER_SPECIALSTATS)
            w->w_Flags = (w->w_Flags & ~WF_ACTIVE) | WF_OFF;
      }
      rc = 1;
   }

   return rc;
}
/*E*/
/*F*/ PRIVATE UWORD updatetracktype(GD gd)
{
   struct PublicData *pd = gd->gd_Public;
   struct TrackType *tt;
   ULONG i;
   UWORD rc = 0;

   for(i=gd->gd_NumTT, tt=pd->pd_TrackedTypes; i; --i, ++tt)
   {
      if (tt->tt_Flags & TTF_ACTIVE)
      {
         pd->pd_S2Req.ios2_StatData = (void*)&tt->tt_Stats;
         pd->pd_S2Req.ios2_Req.io_Command = S2_GETTYPESTATS;
         if (DoIO((struct IORequest*)&pd->pd_S2Req))
         {
            struct Watch *w;

            SCANWATCHES(w, &gd->gd_Watches)
            {
               if (w->w_TrackType == tt)
                  w->w_Flags = (w->w_Flags & ~WF_ACTIVE) | WF_OFF;
            }
            rc = 1;
         }
      }
   }
   return rc;
}
/*E*/
/*F*/ PRIVATE VOID updategui(GD gd, BOOL complete)
{
   struct Watch *w;

   if (complete)
   {
      SCANWATCHES(w, &gd->gd_Watches)
      {
         if (w->w_Flags & WF_ACTIVE)
         {
            LT_SetAttributes(gd->gd_Handle, w->w_ID,
                                    GTNM_Number, *(w->w_Value),
                                    TAG_DONE);
         }
      }
   }
   else
   {
      SCANWATCHES(w, &gd->gd_Watches)
      {
         if ((w->w_Flags & (WF_ACTIVE | WF_STATIC)) == WF_ACTIVE)
         {
            LT_SetAttributes(gd->gd_Handle, w->w_ID,
                                    GTNM_Number, *(w->w_Value),
                                    TAG_DONE);
         }
      }
   }
   return ;
}
/*E*/

/*F*/ STATIC ULONG strntolong(GD gd, STRPTR s, LONG *number, LONG n)
{

#define IsDigit(c) ((c) >= '0' && (c) <= '9'))
#define IsXDigit(c) (((c) >= '0' && (c) <= '9') || ((c) >= 'A' && (c) <= 'F'))

   BOOL hex;
   UBYTE c;
   UWORD countdown;
   ULONG preconv, rc;

   if ((n > 2) && !Strnicmp(s, "0x", 2))
   {
      hex = TRUE;
      s += 2;
      n -= 2;
      preconv = 2;
   }
   else if ((n > 1) && !Strnicmp(s, "$", 1))
   {
      hex = TRUE;
      ++s;
      --n;
      preconv = 1;
   }
   else
   {
      hex = FALSE;
      preconv = 0;
   }

   if (countdown=n)
   {
      LONG num;

      for(num=0; countdown && (*s != '\0'); ++s, --countdown)
      {
         c = ToUpper((ULONG)*s);
         if (hex)
         {
            if (c >= '0' && c <= '9') num = num * 16 + c - '0';
            else if (c >= 'A' && c <= 'F') num = num * 16 + c - '0';
            else break;
         }
         else
         {
            if (c >= '0' && c <= '9') num = num * 10 + c - '0';
            else break;
         }
      }

      if (n - countdown)
      {
         *number = num;
         rc = preconv + (n - countdown);
      }
      else rc = 0;
   }
   else rc = 0;

   return rc;
}
/*E*/
/*F*/ PRIVATE VOID message(GD gd, LONG title, LONG body, LONG button, ...)
{
   struct EasyStruct es;

   es.es_StructSize = sizeof(struct EasyStruct);
   es.es_Flags = 0;
   es.es_Title = GetString(&gd->gd_LocaleInfo, title);
   es.es_TextFormat = GetString(&gd->gd_LocaleInfo, body);
   es.es_GadgetFormat = GetString(&gd->gd_LocaleInfo, button);

   if (gd->gd_Handle)
   {
      LT_LockWindow(gd->gd_Handle->Window);
      EasyRequestArgs(gd->gd_Handle->Window, (struct EasyStruct *)&es, NULL, (APTR)(&button + 1));
      LT_UnlockWindow(gd->gd_Handle->Window);
   }
   else
   {
      if (gd->gd_CLI || !IntuitionBase)
      {
         if (gd->gd_DOSBase) VPrintf(es.es_TextFormat, (APTR)(&button + 1));
         VPrintf("\n", NULL);
      }
      else
         EasyRequestArgs(NULL, (struct EasyStruct *)&es, NULL, (APTR)(&button + 1));
   }
}
/*E*/
/*F*/PRIVATE VOID closegui(GD gd)
{
   if (gd->gd_Handle)
   {
      gd->gd_Left = gd->gd_Handle->Window->LeftEdge;
      gd->gd_Top = gd->gd_Handle->Window->TopEdge;

      LT_DeleteHandle(gd->gd_Handle);
      gd->gd_Handle = NULL;
   }
   if (gd->gd_Menu)
   {
      LT_DisposeMenu(gd->gd_Menu);
      gd->gd_Menu = NULL;
   }
   return ;
}
/*E*/
/*F*/ PRIVATE BOOL opengui(GD gd)
{
   struct PublicData *pd = gd->gd_Public;
   BOOL rc = FALSE, vgroup;
   int i;
   struct Watch *w;

   if (gd->gd_Handle = LT_CreateHandleTags(NULL,
                                 LH_AutoActivate,FALSE,
                                 LH_UserData, gd,
                                 LAHN_MenuGlyphs, TRUE,
                                 gd->gd_CustomFont ? LH_Font : TAG_IGNORE, &pd->pd_Font,
                                 pd->pd_Pubscreen[0] ? LAHN_PubScreenName : TAG_IGNORE, pd->pd_Pubscreen,
                                 TAG_DONE))
   {
      vgroup = FALSE;
      i = 0;
      LT_New(gd->gd_Handle, LA_Type, VERTICAL_KIND, TAG_DONE);
      SCANWATCHES(w, &gd->gd_Watches)
      {
         if ((w->w_Flags & WF_FIRSTTT) && (w->w_TrackType->tt_Flags & TTF_ACTIVE))
         {
            if (vgroup)
            {
               /*vgroup = FALSE;*/
               LT_EndGroup(gd->gd_Handle);
            }
            LT_New(gd->gd_Handle, LA_Type, VERTICAL_KIND, LA_LabelText, w->w_TrackType->tt_Label, TAG_DONE);
         }

         if (w->w_Flags & WF_ACTIVE)
         {
            if (!vgroup)
            {
               vgroup = TRUE;
               LT_New(gd->gd_Handle, LA_Type, VERTICAL_KIND, TAG_DONE);
            }
            LT_New(gd->gd_Handle, LA_Type, NUMBER_KIND,
                                  LA_LabelText, w->w_Label,
                                  /*w->w_Format ? GTNM_Format : TAG_IGNORE, w->w_Format,*/
                                  LA_ID, w->w_ID = i,
                                  LA_NoKey, TRUE,
                                  LA_Chars, 6,
                                  w->w_Value ? GTNM_Number : TAG_IGNORE, *(w->w_Value),
                                  LA_ExtraSpace, -1,
                                  ((GadToolsBase->lib_Version > 40) ||
                                  ((GadToolsBase->lib_Version == 40) && (GadToolsBase->lib_Revision >= 1)))  ?
                                       GTNM_Justification : TAG_IGNORE, GTJ_RIGHT,
                                  TAG_DONE);
         }
         ++i;
      }
      if (vgroup) LT_EndGroup(gd->gd_Handle);
      LT_EndGroup(gd->gd_Handle);

      checkmenu(gd);
      if (gd->gd_Menu = LT_NewMenuTemplate(gd->gd_Handle->Screen, NULL,
                                           gd->gd_Handle->AmigaGlyph,
                                           gd->gd_Handle->CheckGlyph,
                                           NULL, gd->gd_NewMenu))
      {
         sprintf(gd->gd_WindowTitle, "%s/%ld",
                     pd->pd_S2Req.ios2_Req.io_Device->dd_Library.lib_Node.ln_Name, gd->gd_Unit);

         if (LT_Build(gd->gd_Handle, LAWN_Title,        gd->gd_WindowTitle,
                                     LAWN_IDCMP,        IDCMP_CLOSEWINDOW,
                                     LAWN_SmartZoom,    TRUE,
                                     LAWN_Menu,         gd->gd_Menu,
                                     LAWN_Left,         gd->gd_Left,
                                     LAWN_Top,          gd->gd_Top,
                                     WA_Activate,       gd->gd_Inactive ? FALSE : TRUE,
                                     WA_DepthGadget,    TRUE,
                                     WA_DragBar,        TRUE,
                                     WA_CloseGadget,    TRUE,
                                     TAG_DONE))
         {
            ULONG pen = gd->gd_Handle->DrawInfo->dri_Pens[HIGHLIGHTTEXTPEN];

            if (gd->gd_ToBack)
            {
               WindowToBack(gd->gd_Handle->Window);
               gd->gd_ToBack = 0;
            }
            gd->gd_Inactive = 0;

            SCANWATCHES(w, &gd->gd_Watches)
            {
               if (w->w_Flags & WF_ACTIVE)
                  LT_SetAttributes(gd->gd_Handle, w->w_ID, GTNM_FrontPen, pen, TAG_DONE);
            }

            rc = TRUE;
         }
         else message(gd, REQ_ERROR_TITLE, REQ_ERROR_NOGUI, REQ_ERROR_QUITBUTTON);
      }
   }
   else message(gd, REQ_ERROR_TITLE, REQ_ERROR_NOGUI, REQ_ERROR_QUITBUTTON);

   return rc;
}
/*E*/
/*F*/ PRIVATE LONG nextkey(STRPTR arg, STRPTR *next)
{
   STRPTR nx;
   LONG len;

   if ((nx = strpbrk(arg, KEYDELIMITERS)) != NULL)
   {
      len = nx - arg;
      ++nx;
   }
   else
   {
      len = strlen(arg);
      nx = arg + len;
   }

   *next = nx;
   return len;
}
/*E*/
/*F*/ PRIVATE BOOL parseargs(GD gd)
{
   PD pd = gd->gd_Public;
   BOOL rc = FALSE;
   LONG param[ARG_COUNT];
#if (USE_EXTREADARGS != 0)
   struct ExtRDArgs eargs = {NULL};

   eargs.erda_Window        = (STRPTR)DEF_CONSOLE;
   eargs.erda_Template      = (STRPTR)TEMPLATE;
   eargs.erda_Parameter     = param;
   eargs.erda_FileParameter = -1;
   eargs.erda_SysBase       = SysBase;
   eargs.erda_DOSBase       = DOSBase;
   eargs.erda_IconBase      = IconBase;
#else
   struct RDArgs *rda;
#endif

   memset(param, '\0', sizeof(param));

#if (USE_EXTREADARGS != 0)
   if (ExtReadArgs(gd->gd_argc, (STRPTR*)gd->gd_argv, &eargs) == 0)
#else
   if (rda = ReadArgs((STRPTR)TEMPLATE, param, NULL))
#endif
   {
      strncpy((char*)gd->gd_Device, DEF_DEVICE, sizeof(gd->gd_Device));
      gd->gd_Unit = DEF_UNIT;
      gd->gd_Refresh = DEF_REFRESH;
      gd->gd_CustomFont = 0;
      gd->gd_Top = DEF_TOP;
      gd->gd_Left = DEF_LEFT;
      strncpy((char*)pd->pd_Console, DEF_CONSOLE, sizeof(pd->pd_Console));

      if (param[ARG_WINDOW])
         strncpy((char*)pd->pd_Console, (char*)param[ARG_PUBSCREEN], sizeof(pd->pd_Console));

      if (param[ARG_LIST])
      {
         gd->gd_List = 1;
         goto bailout;
      }

      if (param[ARG_DEVICE])
         strncpy((char*)gd->gd_Device, (char*)param[ARG_DEVICE], sizeof(gd->gd_Device));

      gd->gd_Force = (param[ARG_FORCE] != 0);

      if (param[ARG_UNIT])
         gd->gd_Unit = *(LONG*)(param[ARG_UNIT]);

      if (param[ARG_REFRESH])
         gd->gd_Refresh = *(LONG*)(param[ARG_REFRESH]);

      if (param[ARG_FONT])
      {
         gd->gd_CustomFont = 1;
         strncpy(pd->pd_Fontname, (char*)param[ARG_FONT], sizeof(pd->pd_Fontname));
         pd->pd_Font.tta_Name = (STRPTR)pd->pd_Fontname;
         pd->pd_Font.tta_YSize = param[ARG_FONTSIZE] ? *(LONG*)(param[ARG_FONTSIZE]) : DEF_FONTSIZE;
         pd->pd_Font.tta_Style = FS_NORMAL;
         pd->pd_Font.tta_Flags = FPF_DISKFONT;
         pd->pd_Font.tta_Tags = NULL;
      }

      if (param[ARG_LEFT])
         gd->gd_Left = *(LONG*)(param[ARG_LEFT]);

      if (param[ARG_TOP])
         gd->gd_Top = *(LONG*)(param[ARG_TOP]);

      if (param[ARG_SHOW])
      {
         STRPTR arg, next;
         struct Watch *w;
         LONG len;

         SCANWATCHES(w, &gd->gd_Watches)
         {
            w->w_Flags &= ~WF_ACTIVE;
         }

         arg = (STRPTR)param[ARG_SHOW];
         while(len = nextkey(arg, &next))
         {
            SCANWATCHES(w, &gd->gd_Watches)
            {
               if (!Strnicmp(w->w_Key, arg, len))
               {
                  w->w_Flags |= WF_ACTIVE;
                  break;
               }
            }
            arg = next;
         }
      }

      if (param[ARG_TRACKTYPE])
      {
         STRPTR arg, next;
         LONG len, clen, i;

         arg = (STRPTR)param[ARG_TRACKTYPE];
         while(len = nextkey(arg, &next))
         {
            ++gd->gd_NumTT;
            arg = next;
         }

         if (pd->pd_TrackedTypes = allocpvec(gd, gd->gd_NumTT * sizeof(struct TrackType)))
         {
            struct TrackType *tt = pd->pd_TrackedTypes;
            STRPTR fmt;

            arg = (STRPTR)param[ARG_TRACKTYPE];
            while(len = nextkey(arg, &next))
            {
               if (clen = strntolong(gd, arg, (LONG*)&tt->tt_Type, len))
               {
                  tt->tt_Flags = 0;
                  fmt = GetString(&gd->gd_LocaleInfo, TXT_TTLABEL);
                  sprintf(tt->tt_Label, fmt, tt->tt_Type);
                  sprintf(tt->tt_Key, "$%lx", tt->tt_Type);
                  if (clen != len)
                  {
                     if (strchr(TTMASKSEPARATOR, arg[clen]) != NULL)
                     {
                        tt->tt_Flags |= TTF_INITIAL_SETUP;
                        for(++clen, i=0; clen < len; ++clen, ++i)
                        {
                           if (strchr(TTMASKACTIVE, arg[clen]) != NULL)
                              tt->tt_Flags |= ttmaskflags[i];
                           else if (strchr(TTMASKINACTIVE, arg[clen]) == NULL)
                           {
                              d(("bad ttmask char: %lc (%02lx)\n",arg[clen],arg[clen]));
                              goto errorbailout;
                           }
                        }
                     }
                  }
                  ++tt;
               }
               else --gd->gd_NumTT;

               arg = next;
            }
         }
      }

      if (param[ARG_NOACTIVATE])
         gd->gd_Inactive = 1;

      if (param[ARG_WINDOWTOBACK])
         gd->gd_ToBack = 1;

      if (param[ARG_PUBSCREEN])
         strncpy((char*)pd->pd_Pubscreen, (char*)param[ARG_PUBSCREEN],
                                                sizeof(pd->pd_Pubscreen));

bailout:
      rc = TRUE;

errorbailout:

#if (USE_EXTREADARGS != 0)
      ExtFreeArgs(&eargs);
#else
      FreeArgs(rda);
#endif
   }
   else if (gd->gd_CLI) PrintFault(IoErr(),"");

   return(rc);
}
/*E*/
/*F*/ PRIVATE VOID openconsole(GD gd)
{
   struct PublicData *pd = gd->gd_Public;

   if (!gd->gd_CLI)
   {
      if (gd->gd_Output = Open(pd->pd_Console, MODE_OLDFILE))
      {
         gd->gd_OldConsoleTask = SetConsoleTask(((struct FileHandle*)BADDR(gd->gd_Output))->fh_Type);
         gd->gd_OldOutput = SelectOutput(gd->gd_Output);
         gd->gd_OutputSelected = 1;
         gd->gd_CTSelected = 1;
         if (gd->gd_Input = Open("*", MODE_OLDFILE))
         {
            gd->gd_OldInput = SelectInput(gd->gd_Input);
            gd->gd_InputSelected = 1;
         }
         else d(("no input\n"));
      }
      else d(("no output\n"));
   }
}
/*E*/
/*F*/ PRIVATE VOID cleanup(GD gd)
{
   PD pd = gd->gd_Public;

   closegui(gd);
   if (pd && pd->pd_TReq.tr_node.io_Device) CloseDevice((struct IORequest*)&pd->pd_TReq);
   if (pd && pd->pd_S2Req.ios2_Req.io_Device)
   {
      struct TrackType *tt;
      ULONG i;
      for(i=gd->gd_NumTT, tt=pd->pd_TrackedTypes; i; --i, ++tt)
      {
         if (tt->tt_Flags & TTF_TRACKED)
         {
            pd->pd_S2Req.ios2_PacketType = tt->tt_Type;
            pd->pd_S2Req.ios2_Req.io_Command = S2_UNTRACKTYPE;
            DoIO((struct IORequest*)&pd->pd_S2Req);
         }
      }
      CloseDevice((struct IORequest*)&pd->pd_S2Req);
   }
   if (gd->gd_Port) DeleteMsgPort(gd->gd_Port);
   if (gd->gd_LocaleInfo.li_Catalog) CloseCatalog(gd->gd_LocaleInfo.li_Catalog);
   if (LocaleBase) CloseLibrary(LocaleBase);
   if (GTLayoutBase) CloseLibrary(GTLayoutBase);
   if (GadToolsBase) CloseLibrary(GadToolsBase);
   if (UtilityBase) CloseLibrary(UtilityBase);
   if (IntuitionBase) CloseLibrary(IntuitionBase);
   if (gd->gd_InputSelected) SelectInput(gd->gd_OldInput);
   if (gd->gd_OutputSelected) SelectOutput(gd->gd_OldOutput);
   if (gd->gd_CTSelected) SetConsoleTask(gd->gd_OldConsoleTask);
   if (gd->gd_Input) Close(gd->gd_Input);
   if (gd->gd_Output) Close(gd->gd_Output);
   if (DOSBase) CloseLibrary(DOSBase);
   if (gd->gd_Public) FreeVec(gd->gd_Public);
   if (gd->gd_Pool) AsmDeletePool(gd->gd_Pool, (struct ExecBase*)SysBase);
   FreeVec(gd);
   return ;
}
/*E*/
/*F*/ PRIVATE BOOL opendevs(GD gd)
{
   struct PublicData *pd = gd->gd_Public;
   BOOL rv = FALSE;
   struct TrackType *tt;
   ULONG i;
   static const struct TagItem dummybuffermgnt[] =
   {
      S2_CopyToBuff, ~0,
      S2_CopyFromBuff, ~0,
      TAG_DONE
   };
   LONG rc;

   gd->gd_We = FindTask(NULL);

   if (gd->gd_Port = CreateMsgPort())
   {
      pd->pd_TReq.tr_node.io_Message.mn_ReplyPort = gd->gd_Port;
      if (!OpenDevice("timer.device", UNIT_VBLANK, (struct IORequest*)&pd->pd_TReq, 0L))
      {
         pd->pd_TReq.tr_node.io_Command = TR_ADDREQUEST;
         gd->gd_time.tv_secs = gd->gd_Refresh;
         gd->gd_time.tv_micro = 0;

         pd->pd_S2Req.ios2_Req.io_Message.mn_ReplyPort = gd->gd_Port;
         if (gd->gd_Force)
            pd->pd_S2Req.ios2_BufferManagement = (VOID*)dummybuffermgnt;

         if (!OpenDevice(gd->gd_Device, gd->gd_Unit, (struct IORequest*)&pd->pd_S2Req, 0L))
         {
            pd->pd_S2DeviceQuery.SizeAvailable = sizeof(pd->pd_S2DeviceQuery);
            pd->pd_S2Req.ios2_StatData = &pd->pd_S2DeviceQuery;
            pd->pd_S2Req.ios2_Req.io_Command = S2_DEVICEQUERY;
            if (!DoIO((struct IORequest*)&pd->pd_S2Req))
            {
               for(i=gd->gd_NumTT, tt=pd->pd_TrackedTypes; i; --i, ++tt)
               {
                  pd->pd_S2Req.ios2_PacketType = tt->tt_Type;
                  pd->pd_S2Req.ios2_Req.io_Command = S2_TRACKTYPE;
                  rc = DoIO((struct IORequest*)&pd->pd_S2Req);
                  if (rc == 0)
                  {
                     tt->tt_Flags |= TTF_TRACKED;
                  }
                  else if ((rc == S2ERR_BAD_STATE) && (pd->pd_S2Req.ios2_WireError == S2WERR_ALREADY_TRACKED))
                  {
                     rc = 0;
                  }
                  else
                  {
                     message(gd, REQ_ERROR_TITLE, REQ_ERROR_TRACK, REQ_ERROR_QUITBUTTON,
                                       pd->pd_S2Req.ios2_PacketType,
                                       pd->pd_S2Req.ios2_Req.io_Error,
                                       pd->pd_S2Req.ios2_WireError);
                     break;
                  }
               }
               if (rc == 0)
               {
                  rv = TRUE;
               }
            }
            else message(gd, REQ_ERROR_TITLE, REQ_ERROR_QUERY, REQ_ERROR_QUITBUTTON,
                                       gd->gd_Device, gd->gd_Unit);
         }
         else message(gd, REQ_ERROR_TITLE, REQ_ERROR_OPENDEV, REQ_ERROR_QUITBUTTON,
                                       gd->gd_Device, gd->gd_Unit);
      }
      else message(gd, REQ_ERROR_TITLE, REQ_ERROR_OPENDEV, REQ_ERROR_QUITBUTTON,
                              "timer.device", UNIT_VBLANK);
   }
   else message(gd, REQ_ERROR_TITLE, REQ_ERROR_NOMSGPORT, REQ_ERROR_QUITBUTTON);

   return rv;
}
/*E*/
/*F*/ PRIVATE struct Library *openlib(GD gd, STRPTR name, LONG version)
{
   struct Library *lib;

   if (!(lib = OpenLibrary(name, version)))
      message(gd, REQ_ERROR_TITLE, REQ_ERROR_OPENLIB, REQ_ERROR_QUITBUTTON,
                                    name, version);
   return lib;
}
/*E*/
/*F*/ PRIVATE GD createenv(ULONG argc, UBYTE **argv)
{
   GD gd, new;
   BOOL ok = FALSE;

   {
      LOCALSYSBASE;
      new = AllocVec(sizeof(struct GlobalData), MEMF_ANY | MEMF_CLEAR);
      new->gd_SysBase = SysBase;
   }

   if (gd = new)
   {
      gd->gd_argv = argv;
      gd->gd_argc = argc;
      if (gd->gd_Public = AllocVec(sizeof(struct PublicData), MEMF_PUBLIC | MEMF_CLEAR))
      {
         if (DOSBase = openlib(gd, "dos.library", 37))
         {
            if (LocaleBase = openlib(gd, "locale.library", 38))
               gd->gd_LocaleInfo.li_Catalog = OpenCatalog(NULL, "sana2meter.catalog",
                                                               OC_Version, 2, TAG_DONE);

            if (UtilityBase = openlib(gd, "utility.library", 37))
            {
               if (IntuitionBase = openlib(gd, "intuition.library", 37))
               {
                  if (GadToolsBase = openlib(gd, "gadtools.library", 37))
                  {
                     if (GTLayoutBase = openlib(gd, "gtlayout.library", 24))
                     {
                        GetProgramName(new->gd_ProgramName, sizeof(new->gd_ProgramName));

                        if (!(gd->gd_CLI = (Cli() != NULL)))
                           IconBase = openlib(gd, "icon.library", 0);

                        if (gd->gd_Pool = AsmCreatePool(POOLKIND, POOLPUDDLE, POOLTHRESH,
                                                      (struct ExecBase*)SysBase))
                        {
                           if (preparewatches(gd))
                           {
                              if (parseargs(gd))
                              {
                                 if (preparetts(gd))
                                 {
                                    openconsole(gd);
                                    ok = TRUE;
                                 }
                              }
                              else
                              {
                                 if (!gd->gd_CLI)
                                    message(gd, REQ_ERROR_TITLE, REQ_ERROR_READARGS,
                                                      REQ_ERROR_QUITBUTTON, IoErr());
                              }
                           }
                           else d(("can't preparewatches()\n"));
                        }
                        else message(gd, REQ_ERROR_TITLE, REQ_ERROR_NOPOOL, REQ_ERROR_QUITBUTTON);
                     }
                  }
               }
            }
         }
         else d(("no dos.library\n"));
      }
      else d(("out of memory\n"));
   }
   else d(("out of memory\n"));

   if (!ok && new)
   {
      cleanup(new);
      new = NULL;
   }
   return new;
}
/*E*/

/*F*/ PRIVATE VOID *allocpvec(GD gd, ULONG len)
{
   ULONG *p;

   if (p = AsmAllocPooled(gd->gd_Pool, len+sizeof(ULONG), (struct ExecBase*)SysBase))
      *p++ = (ULONG)(len+sizeof(ULONG));

   return (VOID*)p;
}
/*E*/
/*F*/PRIVATE VOID freepvec(GD gd, VOID *mem)
{
   ULONG *p = (ULONG*)mem - 1;

   AsmFreePooled(gd->gd_Pool, p, *p, (struct ExecBase*)SysBase);

   return ;
}
/*E*/

/* ------------------------------------------------------------------------- */
/* This code has been grabbed by Jure Vrhovnik's supra.lib library.          */
/* I could not use the library since it assumes a global SysBase symbol      */
/* Except of the PRIVATE identifier the routines below are _exactly_  the    */
/* same as in supra.lib distribution 1.1, though.                            */
/* A word to the AddToolType() call: This routine uses some undocumented     */
/* information about the memory management of disk objects and might crash   */
/* under future releases of icon.library.                                    */
/*F*/ PRIVATE int ToolMatch(char *s1, char *s2)
{
int cmp = 2;
int offs = 0;

    if ((*s1 == '(' || *s2 == '(') && *s1 != *s2) offs = 3;
    if (*s1 == '(') s1++;
    if (*s2 == '(') s2++;
    if (*s1 == ')') {
            if (*(s1+1) == '\0') s1++;
    }

    if (*s2 == ')') {
        if (*(s2+1) == '\0') s2++;
    }


    while (*s1 && *s2) {
        if (*s1 == '\0' && *s2 == '\0') return(0+offs);
        if (*s1 != *s2) return(cmp+offs);
        if (*s1 == '=') cmp=1;
        s1++; s2++;
        if (*s1 == ')') {
            if (*(s1+1) == '\0') s1++;
        }

        if (*s2 == ')') {
            if (*(s2+1) == '\0') s2++;
        }

    }

    if (*s1 == '\0' && *s2 == '\0') return(0+offs);
    else return(cmp+offs);
}
/*E*/
/*F*/ PRIVATE char *AddToolType(GD gd, struct DiskObject *dobj, char *tool)
{
struct FreeList *flist = (struct FreeList *)dobj + sizeof(struct DiskObject);
char ***ttypes = &(dobj->do_ToolTypes);
char *item = (*ttypes)[0];
int change = -1;
int i=0;
char *newmem;
int len;

    while (item != NULL) {
      if (change == -1) { /* We haven't found any matching tooltype yet */
        switch(ToolMatch(item, tool)) {
            case 0: return(item);   /* This tooltype already defined */
            case 1:
            case 3:
            case 4: change = i;     /* Change this tooltype */
        }   /* 2,5 means different */
      }
      i++;
      item = (*ttypes)[i]; /* Get the next tooltype to be examined */
    }

    if (change != -1) {
        len = strlen(tool)+1;
        if ((newmem = AllocMem(len, 0L)) == NULL) return(NULL);
        else if (AddFreeList(flist, newmem, len) == FALSE) {
            FreeMem(newmem, len);
            return(NULL);
        }

        strcpy(newmem, tool);
        (*ttypes)[change] = newmem;
        return(newmem);
    } else {
        len = (i+2)*4;
        if ((newmem = AllocMem(len, 0L)) == NULL) return(NULL);
        else if (AddFreeList(flist, newmem, len) == FALSE) {
            FreeMem(newmem, len);
            return(NULL);
        }

        memcpy(newmem, *ttypes, len);
        *ttypes = (char **)newmem;

        len = strlen(tool)+1;
        if ((newmem = AllocMem(len, 0L)) == NULL) return(NULL);
        else if (AddFreeList(flist, newmem, len) == FALSE) {
            FreeMem(newmem, len);
            return(NULL);
        }

        strcpy(newmem, tool);
        (*ttypes)[i] = newmem;
        (*ttypes)[i+1] = NULL;
        return(newmem);
    }
}
/*E*/
/* ------------------------------------------------------------------------- */
/*F*/PRIVATE VOID saveconfig(GD gd)
{
#define MAXTOOLTYPE 2048

   BPTR old, new;
   struct DiskObject *dob;
   STRPTR tooltype;
   STRPTR progname;

   if (!IconBase)
      IconBase = openlib(gd, "icon.library", 0);

   if (IconBase)
   {
      if (gd->gd_CLI)
      {
         progname = gd->gd_ProgramName;
         new = Lock("PROGDIR:", ACCESS_READ);
      }
      else
      {
         struct WBArg *wbarg = ((struct WBStartup *)gd->gd_argv)->sm_ArgList;
         progname = wbarg->wa_Name;
         new = DupLock(wbarg->wa_Lock);
      }

      if(new)
      {
         old = CurrentDir(new);
         if (dob = GetDiskObject(progname))
         {
            if (tooltype = allocpvec(gd, MAXTOOLTYPE))
            {
               /* SHOW watches */
               strcpy(tooltype, "SHOW=");
               {
                  struct Watch *w;
                  BOOL first = TRUE;

                  SCANWATCHES(w, &gd->gd_Watches)
                  {
                     if ((w->w_Member != MEMBER_TRACKTYPE) && (w->w_Flags & WF_ACTIVE) && w->w_Key)
                     {
                        if (first)
                           first = FALSE;
                        else
                           strcat(tooltype, ",");
                        strcat(tooltype, w->w_Key);
                     }
                  }
                  AddToolType(gd, dob, tooltype);
               }

               /* TRACKTYPE watches */
               strcpy(tooltype, "TRACKTYPE=");
               {
                  struct Watch *w;
                  BOOL first = TRUE;

                  SCANWATCHES(w, &gd->gd_Watches)
                  {
                     if (w->w_Member == MEMBER_TRACKTYPE)
                     {
                        if (w->w_Key)
                        {
                           if (first)
                              first = FALSE;
                           else
                              strcat(tooltype, ",");

                           sprintf(&tooltype[strlen(tooltype)], "%s%lc",
                                 w->w_Key, (LONG)TTMASKSEPARATOR[0]);
                        }
                        sprintf(&tooltype[strlen(tooltype)], "%lc",
                           (w->w_Flags & WF_ACTIVE) ? TTMASKACTIVE[0] : TTMASKINACTIVE[0]);
                     }
                  }
                  AddToolType(gd, dob, tooltype);
               }

               /* window position */
               sprintf(tooltype, "LEFT=%ld", gd->gd_Handle->Window->LeftEdge);
               AddToolType(gd, dob, tooltype);
               sprintf(tooltype, "TOP=%ld", gd->gd_Handle->Window->TopEdge);
               AddToolType(gd, dob, tooltype);

               /* refresh interval */
               sprintf(tooltype, "REFRESH=%ld", gd->gd_Refresh);
               AddToolType(gd, dob, tooltype);

               freepvec(gd, tooltype);
            }
            PutDiskObject(progname, dob);
         }
         UnLock(CurrentDir(old));
      }
      CloseLibrary(IconBase);
   }

   return ;
}
/*E*/

/*F*/ PUBLIC VOID SAVEDS STDARGS main(ULONG argc, UBYTE **argv)
{
   int rc = RETURN_FAIL;
   ULONG m_wait, m_port, m_recv, m_window;
   BOOL running;
   GD gd;
   PD pd;

   if (!(gd = createenv(argc, argv))) goto leave;
   pd = gd->gd_Public;

   if (gd->gd_List)
   {
      const struct NewWatch *nw;
      Printf("\033[1mTable of keywords:\033[0m\n");
      for (nw = stdwatches; nw->nw_Member != MEMBER_NONE; nw++)
         Printf("%s\n", nw->nw_Key);
      rc = RETURN_OK;
      goto leave;
   }

   if (!opendevs(gd)) goto leave;

   updateupdate(gd);
   if (!updateglobalstats(gd) || !updatespecialstats(gd) || !updatetracktype(gd))
   {
      updateupdate(gd);
      updateglobalstats(gd);
      updatespecialstats(gd);
      updatetracktype(gd);
   }
   getspeciallabels(gd);

   if (!preparemenu(gd)) goto leave;

   if (!opengui(gd)) goto leave;

   updategui(gd, TRUE);

   m_wait = SIGBREAKF_CTRL_C |
            (m_port = 1 << gd->gd_Port->mp_SigBit) |
            (m_window = 1 << gd->gd_Handle->Window->UserPort->mp_SigBit);

   pd->pd_TReq.tr_time = gd->gd_time;
   SendIO((struct IORequest*)&pd->pd_TReq);

   rc = RETURN_OK;
   for(running = TRUE; running;)
   {
      m_recv = Wait(m_wait);

      if (m_recv & SIGBREAKF_CTRL_C)
      {
         running = FALSE;
      }

      if (m_recv & m_window)
      {
         struct IntuiMessage *im;
         ULONG qualifier;
         ULONG class;
         UWORD code;
         struct Gadget *gadget;

         while(im = GT_GetIMsg(gd->gd_Handle->Window->UserPort))
         {
            class     = im->Class;
            code      = im->Code;
            qualifier = im->Qualifier;
            gadget    = im->IAddress;

            GT_ReplyIMsg(im);

            LT_HandleInput(gd->gd_Handle, qualifier, &class, &code, &gadget);

            switch(class)
            {
               case IDCMP_CLOSEWINDOW:
                   running = FALSE;
               break;

               case IDCMP_MENUPICK:
               {
                  UWORD n_menu, n_item, n_subitem;
                  struct MenuItem *it;
                  BOOL update = FALSE;

                  while(it = ItemAddress(gd->gd_Menu, code))
                  {
                     n_menu = MENUNUM(code);
                     n_item = ITEMNUM(code);
                     n_subitem = SUBNUM(code);

                     if (n_menu == 0)
                     {
                        if (n_item == 0)
                           saveconfig(gd);

                        if (n_item == 2)
                        {
                           message(gd, REQ_ABOUT_TITLE, REQ_ABOUT_BODY, REQ_ABOUT_BUTTON,
                              VERSION, REVISION, EMAIL_AUTHOR);
                        }

                        if (n_item == 4)
                           running = FALSE;
                     }
                     else if (n_menu == 1)
                     {
                        if (n_item == 0)
                        {
                           gd->gd_time.tv_secs = intervals[n_subitem];
                           AbortIO((struct IORequest*)&pd->pd_TReq);
                        }
                     }
                     else if (n_menu == 2)
                     {
                        struct Watch *w;

                        if (w = (struct Watch *)GTMENUITEM_USERDATA(it))
                        {
                           if (it->Flags & CHECKED)
                              w->w_Flags |= WF_ACTIVE;
                           else
                              w->w_Flags &= ~WF_ACTIVE;
                           update = TRUE;
                        }
                     }
                     code = it->NextSelect;
                  }

                  if (update)
                  {
                     UWORD fail = 0;

                     AbortIO((struct IORequest*)&pd->pd_TReq);
                     closegui(gd);
                     updateupdate(gd);
                     if (gd->gd_GetGlobalStats) fail = updateglobalstats(gd);
                     if (gd->gd_GetSpecialStats) fail += updatespecialstats(gd);
                     if (gd->gd_GetTrackType) fail += updatetracktype(gd);
                     if (fail) updateupdate(gd);
                     if (!opengui(gd)) running = FALSE;
                  }
               }
               break;

               case IDCMP_GADGETUP:
               break;
            }
         }
      }

      if (m_recv & m_port)
      {
         UWORD fail = 0;

         WaitIO((struct IORequest*)&pd->pd_TReq);

         if (gd->gd_GetGlobalStats) fail = updateglobalstats(gd);
         if (gd->gd_GetSpecialStats) fail += updatespecialstats(gd);
         if (gd->gd_GetTrackType) fail += updatetracktype(gd);
         if (fail)
         {
            closegui(gd);
            updateupdate(gd);
            if (!opengui(gd)) running = FALSE;
         }
         else
            updategui(gd, FALSE);

         pd->pd_TReq.tr_time = gd->gd_time;
         SendIO((struct IORequest*)&pd->pd_TReq);
      }
   }

   AbortIO((struct IORequest*)&pd->pd_TReq);
   WaitIO((struct IORequest*)&pd->pd_TReq);

leave:
   if (gd) cleanup(gd);

   exit(rc);
   return ;
}
/*E*/

