/*
 * wmnd: main program module
 *
 * Copyright(c) 2000-2001 by Reed Lai
 * Copyright(c) 2001 by Timecop <timecop@japan.co.jp>
 * Copyright(c) 2001-2008 by wave++ "Yuri D'Elia" <wavexx@users.sf.net>
 *
 * Distributed under GNU GPL (v2 or above) WITHOUT ANY WARRANTY.
 */

#include "wmnd.h"
#include "cfgdata.h"
#include "display.h"
#include "misc.h"
#include "master.xpm"
#include "drivers.h"
#include "messages.h"


struct var *vars;        /* config file option structure */
struct Devices *devices; /* devices to monitor */
DevTable wmnd;           /* interesting information about devices */
Dockapp dockapp;         /* dockapp structure */
MRegion mr[32];          /* mouse regions */


/* GUI */
static void redraw_window(void);
unsigned long get_color(char* name);
void new_window(char* res_name, char* res_class,
    int width, int height, int argc, char** argv);

/* config file read/write */
void conf_read(char* filename);
void conf_write(char* filename);
void assign(char* name, char* value);
struct var *lookup(char* name);
char* value(char* name);
char* vcopy(char* str);

/* utils for command line */
int strval_fe(const struct pair_strint *data, const char* val);
int defcon_lk(const char* token);
void defcon_touch(char* token, char* val);

/* support */
int add_mr(int index, int x, int y, int width, int height);
int check_mr(int x, int y);
void beat_event(void);
void click_event(unsigned int region, unsigned int button);
static void led_control(const unsigned char led, const unsigned char mode);
void scale(char* rx_buf, char* tx_buf, unsigned long rx,
    unsigned long tx, const int gap);
void metric_scale(unsigned char sign, unsigned long value, char* buf);
void binary_scale(unsigned char sign, unsigned long value, char* buf);
void draw_string(const char* buf, unsigned int x, unsigned int y);
void smooth(unsigned long* stat, const unsigned long last, const float smooth);

/* device statistics */
void draw_interface(void);
void draw_rate(unsigned long rx, unsigned long tx, const int gap);
void draw_max(unsigned long rx, unsigned long tx);
void draw_stats(struct Devices *ptr, const int gap);

/* driver functions */
int devices_init(const char* driver, const char* interface);
void devices_select(const char* interface);
void devices_prev(void);
void devices_getstat(struct Devices *device, unsigned long* ip,
    unsigned long* op, unsigned long* ib, unsigned long* ob);
void devices_destroy(void);

/* useless shit */
void reaper(int sig);
void usage(void);
void printversion(void);


int
add_mr(int index, int x, int y, int width, int height)
{
  mr[index].enable = 1;
  mr[index].x = x;
  mr[index].y = y;
  mr[index].width = width;
  mr[index].height = height;
  return 0;
}


int
check_mr(int x, int y)
{
  register int i;
  register int found = 0;

  for(i = 0; i < 32 && !found; i++)
  {
    if(mr[i].enable && x >= mr[i].x &&
	x <= mr[i].x + mr[i].width &&
	y >= mr[i].y && y <= mr[i].y + mr[i].height)
      found = 1;
  }
  if(!found)
    return REG_NOREG;
  return (i - 1);
}


static void
redraw_window(void)
{
  if(dockapp.update)
  {
    msg_dbg(__POSITION__, "redrawing window");
    XCopyArea(dockapp.d, dockapp.pixmap, dockapp.iconwin, dockapp.gc,
	0, 0, dockapp.width, dockapp.height, 0, 0);
    XCopyArea(dockapp.d, dockapp.pixmap, dockapp.win, dockapp.gc,
	0, 0, dockapp.width, dockapp.height, 0, 0);
    dockapp.update = 0;
  }
}


unsigned long
get_color(char* name)
{
  XColor color;
  XWindowAttributes attr;

  color.pixel = 0;

  XGetWindowAttributes(dockapp.d, DefaultRootWindow(dockapp.d), &attr);
  XParseColor(dockapp.d, DefaultColormap(dockapp.d,
      DefaultScreen(dockapp.d)), name, &color);
  color.flags = DoRed | DoGreen | DoBlue;
  XAllocColor(dockapp.d, attr.colormap, &color);
  msg_dbg(__POSITION__, "pixel: %08lx", color.pixel);
  return color.pixel;
}


void
new_window(char* res_name, char* res_class,
    int width, int height, int argc, char** argv)
{
  char* geometry;
  XpmAttributes attr;
  XpmColorSymbol cols[3] =
  {
    {"rx_color", NULL, 0},
    {"tx_color", NULL, 0},
    {"md_color", NULL, 0}
  };
  Pixel fg, bg;
  XGCValues gcval;
  XSizeHints sizehints;
  XClassHint classhint;
  XWMHints wmhints;

  dockapp.width = width;
  dockapp.height = height;
  dockapp.screen = DefaultScreen(dockapp.d);
  dockapp.root = DefaultRootWindow(dockapp.d);

  /* parse the geometry spec, if any */
  geometry = value("geometry");
  if(geometry)
  {
    unsigned dummy;
    XParseGeometry(geometry, &dockapp.x, &dockapp.y, &dummy, &dummy);
  }

  sizehints.flags = USSize | (geometry? USPosition: 0);
  sizehints.x = dockapp.x;
  sizehints.y = dockapp.y;
  sizehints.width = width;
  sizehints.height = height;

  fg = BlackPixel(dockapp.d, dockapp.screen);
  bg = WhitePixel(dockapp.d, dockapp.screen);

  dockapp.win = XCreateSimpleWindow(dockapp.d, dockapp.root,
      sizehints.x, sizehints.y, sizehints.width, sizehints.height, 1, fg, bg);
  dockapp.iconwin = XCreateSimpleWindow(dockapp.d, dockapp.win,
      sizehints.x, sizehints.y, sizehints.width, sizehints.height, 1, fg, bg);

  XSetWMNormalHints(dockapp.d, dockapp.win, &sizehints);
  classhint.res_name = res_name;
  classhint.res_class = res_class;
  XSetClassHint(dockapp.d, dockapp.win, &classhint);

#define EVENTS (ExposureMask | ButtonPressMask | \
	ButtonReleaseMask | StructureNotifyMask)
  XSelectInput(dockapp.d, dockapp.win, EVENTS);
  XSelectInput(dockapp.d, dockapp.iconwin, EVENTS);

  XStoreName(dockapp.d, dockapp.win, res_name);
  XSetIconName(dockapp.d, dockapp.win, res_name);

  gcval.foreground = fg;
  gcval.background = bg;
  gcval.graphics_exposures = False;

  dockapp.gc = XCreateGC(dockapp.d, dockapp.win,
      GCForeground | GCBackground | GCGraphicsExposures, &gcval);

  cols[0].pixel = get_color(value("rx_color"));
  cols[1].pixel = get_color(value("tx_color"));
  cols[2].pixel = get_color(value("md_color"));
  dockapp.stdColors.txColor = cols[1].pixel;
  dockapp.stdColors.rxColor = cols[0].pixel;
  dockapp.stdColors.mdColor = cols[2].pixel;

  attr.exactColors = 0;
  attr.alloc_close_colors = 1;
  attr.closeness = 1L << 15;
  attr.colorsymbols = cols;
  attr.numsymbols = 3;
  attr.valuemask =
    (XpmColorSymbols | XpmExactColors | XpmAllocCloseColors | XpmCloseness);
  if(XpmCreatePixmapFromData
      (dockapp.d, dockapp.win, master_xpm, &dockapp.pixmap,
       &dockapp.mask, &attr) != XpmSuccess)
  {
    msg_err("not enough colors!");
    exit(-1);
  }
  XShapeCombineMask(dockapp.d, dockapp.win, ShapeBounding, 0, 0,
      dockapp.mask, ShapeSet);
  XShapeCombineMask(dockapp.d, dockapp.iconwin, ShapeBounding, 0, 0,
      dockapp.mask, ShapeSet);

  wmhints.initial_state = WithdrawnState;
  wmhints.flags = StateHint;
  wmhints.icon_window = dockapp.iconwin;
  wmhints.icon_x = sizehints.x;
  wmhints.icon_y = sizehints.y;
  wmhints.window_group = dockapp.win;
  wmhints.flags =
    StateHint | IconWindowHint | IconPositionHint | WindowGroupHint;
  XSetWMHints(dockapp.d, dockapp.win, &wmhints);

  XSetCommand(dockapp.d, dockapp.win, argv, argc);
  XSetCommand(dockapp.d, dockapp.iconwin, argv, argc);

  XMapWindow(dockapp.d, dockapp.win);
  dockapp.xfd = ConnectionNumber(dockapp.d);
}


/* support function - chop cr/lf off the end of a string */
void
chomp(char* buffer)
{
  int i = strlen(buffer) - 1;
  while (buffer[i] == '\n' || buffer[i] == '\r')
    buffer[i--] = '\0';
}


void
conf_read(char* filename)
{
  FILE *fp;
  char buf[1024];
  size_t pos;
  int line = 0;
  int exist;

  fp = fopen(filename, "r");

  if(fp)
  {
    /* actually read in the config file, skipping over short and #lines */
    while (fgets(buf, 1024, fp)) {
      line++;
      if(buf[0] == '#' || (strlen(buf) < 2))
	continue;
      chomp(buf);
      pos = strcspn(buf, "=");
      if(pos < strlen(buf))
      {
	buf[pos++] = '\0';

	/* check for existent value */
	exist = defcon_lk(buf); /* search for buf in defaults */
	if(exist != -1 && !pss_defcon[exist].used)
	  exist = -1;
	if(exist == -1) /* only if isn't a default or it's not on cmdline */
	  assign(buf, buf+pos);
      }
      memset(buf, 0, 1024);
    }
    fclose(fp);
  }
  else
  {
    /* can't open the rc file, make a new one */
    msg_err("can't open WMND rc file '%s', using defaults", filename);
    conf_write(filename);
  }
}


void
conf_write(char* filename)
{
  FILE *fp;
  struct var *vp;
  int dc;

  fp = fopen(filename, "w");
  if(!fp)
  {
    msg_err("can't open '%s' for writing", filename);
    exit(1);
  }
  fprintf(fp, "# WMND configuration file (generated automatically)\n\n");

  /* write only changed values */
  for(vp = vars; vp; vp = vp->v_next)
  {
    if(!vp->v_value)
      continue;

    dc = defcon_lk(vp->v_name);
    if(dc == -1 || !pss_defcon[dc].value ||
	strcmp(pss_defcon[dc].value, vp->v_value))
      fprintf(fp, "%s=%s\n", vp->v_name, vp->v_value);
  }
  fclose(fp);
}


void
assign(char* name, char* value)
{
  struct var* vp;
  struct var* newv;

  /* search for existing values */
  for(vp = vars; vp &&
	(vp->v_next || !strcmp(name, vp->v_name)); vp = vp->v_next)
  {
    if(!vp->v_next || !strcmp(name, vp->v_name))
    {
      /* existing value */
      if(vp->v_value)
	free(vp->v_value);
      vp->v_value = vcopy(value);
      return;
    }
  }

  /* append the value */
  newv = (struct var*)malloc(sizeof(struct var));
  newv->v_name = vcopy(name);
  newv->v_value = vcopy(value);
  newv->v_next = NULL;

  if(!vars)
    vars = newv;
  else
    vp->v_next = newv;
}


char*
vcopy(char* str)
{
  if(str == NULL)
    return NULL;

  return strdup(str);
}


int
strval_fe(const struct pair_strint *data, const char* val)
{
  /* returns the correct value for val as int */
  int cnt = 0;
  while (data[cnt].strval)
  {
    if(!strcmp(val,data[cnt].strval))
      return data[cnt].val;
    cnt++;
  }

  /* not found, return 0 */
  return data->val;
}


int
waveval_fe(const struct drwStruct *data, const char* val)
{
  int cnt = 0;
  while (data[cnt].funcName)
  {
    if(!strcmp(val,data[cnt].funcName))
      return cnt;
    cnt++;
  }
  return 0;
}


/* Lookup for an index in the defcon table */
int
defcon_lk(const char* token)
{
  int rtval = 0;
  while (pss_defcon[rtval].token)
  {
    if(!strcmp(token,pss_defcon[rtval].token))
      return rtval;
    rtval++;
  }
  return -1;
}


/* Touch an item in the defcon table (mark as 'used') */
void
defcon_touch(char* token, char* val)
{
  int idx = defcon_lk(token);

  assign(token, val);
  if(idx != -1)
    pss_defcon[idx].used = 1;
}


struct var*
lookup(char* name)
{
  struct var *vp;

  for(vp = vars; vp; vp = vp->v_next)
    if(!strcmp(name, vp->v_name))
      return vp;
  return NULL;
}


char*
value(char* name)
{
  struct var *vp;

  if(!(vp = lookup(name)))
    return NULL;
  return vp->v_value;
}


void
beat_event(void)
{
  unsigned long diff;
  unsigned int min, hr;
  char temp[16];

  msg_dbg(__POSITION__, "activated");
  if(!wmnd.curdev->online)
  {
    if(!bit_get(RUN_ONLINE))
    {
      bit_set(RUN_ONLINE);
      led_control(LED_POWER, 1);
    }
  }
  else
  {
    if(bit_get(RUN_ONLINE))
    {
      bit_off(RUN_ONLINE);
      led_control(LED_POWER, 0);
    }
  }
  if(bit_get(CFG_SHOWTIME) && wmnd.curdev->devstart)
  {
    diff = (long unsigned)(difftime(time(NULL), wmnd.curdev->devstart) / 60);
    min = diff % 60;
    hr = (diff / 60) % 100;
    sprintf(temp, "%02d.%02d", hr, min);
    draw_string(temp, 71, 36);
  }
}


void
exec_perc_command(const char* cmd, int button)
{
  char buf[4];
  char* newcmd;
  perctbl_t data[3];

  /* button */
  sprintf(buf, "%d", button);
  data[0].c = 'b';
  data[0].value = buf;

  /* interface name */
  data[1].c = 'i';
  data[1].value = wmnd.curdev->name;

  /* status */
  data[2].c = 's';
  data[2].value = (wmnd.curdev->online? "0": "1");

  /* parse and exec the new command */
  newcmd = percsubst(cmd, data, 3);
  exec_command(newcmd);
  free(newcmd);
}


#ifdef USE_TREND
/* static data for trend instance sharing */
static FILE** trendFd;
static int trendUpd;


static inline int
trend_idx(int d, int bp)
{
  return(d * 2 + bp);
}


void
close_trend(int i)
{
  if(trendFd[i])
  {
    pclose(trendFd[i]);
    trendFd[i] = NULL;
  }
}


void
close_trends()
{
  unsigned i;
  for(i = 0; i != wmnd.nr_devices * 2; ++i)
    close_trend(i);
  free(trendFd);
}


int
check_trend(int i)
{
  return(fputs("\n", trendFd[i]) == EOF || fflush(trendFd[i]) == EOF);
}


void
exec_trend(struct Devices* dev, int bp)
{
  int d = dev->devnum;
  int i = trend_idx(d, bp);
  int dead = (!trendFd[i] || check_trend(i));

  if(dead)
  {
    /* new trend instance required */
    char cmd[256];

    /* cmdline */
    snprintf(cmd, sizeof(cmd),
	"trend -t '%s %s' -G %d -d -s -c2a -Lin,out %s - %s",
	dev->name, (bp? "bytes": "packets"),
	(wmnd.scale == binary_scale? 1024: 1000) / (bp? 1: 100),
	value("trend_flags"), value("trend_history"));

    /* execute */
    close_trend(i);
    if(!(trendFd[i] = popen(cmd, "w")))
    {
      msg_err("cannot execute trend!");
      return;
    }
  }

  if(dead || !trendUpd)
  {
    /* refill the pipe */
    int x, idx;
    idx = (bp? 0: 2);
    for(x = 0; x != 58; ++x)
      fprintf(trendFd[i], "%lu %lu\n",
	  dev->his[x][idx + 0], dev->his[x][idx + 1]);
    fflush(trendFd[i]);
  }
}
#endif


void
click_event(unsigned int region, unsigned int button)
{
  char* action = NULL;
  if(region == REG_NOREG) /* no region */
    return;

  msg_dbg(__POSITION__, "clicked btn %d in region %d", button, region);

  /* the wheel is valid everywhere */
  if(button == Button4 || button == Button5)
  {
    if(button == Button4)
      devices_prev();
    else
      devices_select(NULL);

    draw_interface();
  }
  else if(region == REG_DEV)
  {
    switch(button)
    {
    case Button1:
      devices_select(NULL);
      draw_interface();
      break;

    case Button3:
      bit_tgl(CFG_SHORTNAME);
      msg_dbg(__POSITION__, "shortname: %d", bit_get(CFG_SHORTNAME));
      draw_interface();
      break;
    }
  }
  else if(region == REG_RT_PB)
  {
    if(button == Button1)
    {
      bit_tgl(CFG_MODE);
      led_control(LED_POWER, bit_get(RUN_ONLINE));
    }
  }
  else if(region == REG_MAIN)
  {
    if(button == Button1)
    {
      if(wmnd.wavemode < (wmnd.nWavemodes - 1))
	wmnd.wavemode++;
      else
	wmnd.wavemode = 0;
      msg_dbg(__POSITION__, "wavemode: %d", wmnd.wavemode);
    }
    else if(button == Button3)
      /* switch time visualization */
      bit_tgl(CFG_SHOWTIME);
  }
  else if(region == REG_SCALE_RX || region == REG_SCALE_TX)
  {
    switch(button)
    {
    case Button1:
      /* switch max screen/history */
      bit_tgl(CFG_MAXSCREEN);
      break;
#ifdef USE_TREND
    case Button2:
      /* launch trend */
      exec_trend(wmnd.curdev, bit_get(CFG_MODE));
      break;
#endif
    case Button3:
      /* toggle max display */
      bit_tgl(CFG_SHOWMAX);
      break;
    }
  }
  else if(region == REG_SCRIPT)
  {
    switch(button)
    {
    case Button1:
      action = value("bt1_action");
      break;
    case Button2:
      action = value("bt2_action");
      break;
    case Button3:
      action = value("bt3_action");
      break;
    }
    if(action)
      exec_perc_command(action, button);
  }
}


void
smooth(unsigned long* stat, const unsigned long last, const float smooth)
{
  *stat = ((unsigned long)(last + (smooth * (*stat - last))));
}


void
mainExit(int sig)
{
  msg_info("caught signal %d, terminating...", sig);
#ifdef USE_TREND
  close_trends();
#endif
  devices_destroy();
  exit(1);
}


int main(int argc, char* *argv)
{
  char* dispname = NULL;
  char* active_interface = NULL;
  char* drv_interface = NULL;
  char* drv_name = NULL;
  char* win_name = NULL;
  int parse_conf = 1;
  char* conf_file = NULL;
  struct Devices* ptr;
  unsigned long ib, ob, ip, op;
  int ch;
  unsigned btn = 0;
  int rgn = -1;
  XEvent event;
  const struct drwStruct* drwPtr;
  sigset_t masked;
  struct timeval beat_time;

  /* initialize messaging functions */
  msg_prgName = argv[0];

  msg_dbg(__POSITION__, "wmnd start");

  /* detect the number of avaible wave modes */
  for(drwPtr = drwFuncs; drwPtr->funcName; ++drwPtr)
    ++wmnd.nWavemodes;
  msg_dbg(__POSITION__, "detected %d display modes", wmnd.nWavemodes);

  /*
   * set default config options before option parsing so command line can
   * overwrite config options
   */
  wmnd.nr_devices = 0;
  wmnd.flags = 0;
  vars = NULL;
  bit_set(RUN_ONLINE);
  bit_set(CFG_MODE);

  /*
   * initalize default values using pss_defcon
   * we can use ch, because it's used later
   */
  ch = 0;
  while(pss_defcon[ch].token)
  {
    assign(pss_defcon[ch].token, pss_defcon[ch].value);
    ch++;
  }

  /* parse command line */
  while((ch =
      getopt(argc, argv, "bc:C:L:d:g:i:hlmMf:Fr:s:S:tvw:D:I:qQo:n:a:")) != EOF)
  {
    switch(ch)
    {
    case 'b':
      defcon_touch("binary_scale", "yes");
      break;
    case 'c':
      defcon_touch("tx_color", optarg);
      break;
    case 'C':
      defcon_touch("rx_color", optarg);
      break;
    case 'L':
      defcon_touch("md_color", optarg);
      break;
    case 'd':
      defcon_touch("display", optarg);
      break;
    case 'g':
      defcon_touch("geometry", optarg);
      break;
    case 'i':
      defcon_touch("interface_name", optarg);
      break;
    case 'l':
      defcon_touch("use_long_names", "yes");
      break;
    case 'm':
      defcon_touch("show_max_values", "no");
      break;
    case 'M':
      defcon_touch("use_max_history", "yes");
      break;
    case 'f':
      conf_file = optarg;
      break;
    case 'F':
      parse_conf = 0;
      break;
    case 'r':
      defcon_touch("refresh", optarg);
      break;
    case 's':
      defcon_touch("scroll", optarg);
      break;
    case 'S':
      defcon_touch("avg_steps", optarg);
      break;
    case 't':
      defcon_touch("display_time", "no");
      break;
    case 'v':
      printversion();
      exit(0);
      break;
    case 'w':
      defcon_touch("wave_mode", optarg);
      break;
    case 'D':
      defcon_touch("driver", optarg);
      break;
    case 'I':
      defcon_touch("driver_interface", optarg);
      break;
    case 'q':
      defcon_touch("quiet", "yes");
      break;
    case 'Q':
      defcon_touch("quiet", "no");
      break;
    case 'o':
      defcon_touch("smooth", optarg);
      break;
    case 'n':
      defcon_touch("name", optarg);
      break;
    case 'a':
      defcon_touch("fixed_max", optarg);
      break;
    default:
      usage();
      exit(0);
      break;
    }
  }
  if(parse_conf)
  {
    if(!conf_file)
    {
      char* tmp;
      tmp = getenv("HOME");
      conf_file = (char*)calloc(1, strlen(tmp) + 9);
      strcat(conf_file, tmp);
      strcat(conf_file, "/.wmndrc");
    }
    conf_read(conf_file);
  }

  /* set stuff here */
  wmnd.refresh = atoi(value("refresh"));
  wmnd.scroll = MAX(atoi(value("scroll")), 1);
  wmnd.avgSteps = MAX(atoi(value("avg_steps")), 1);
  wmnd.smooth = atof(value("smooth"));
  wmnd.maxScale = strtol(value("fixed_max"), NULL, 0);
  win_name = value("name");
  if(strval_fe(psi_bool, value("binary_scale")))
    wmnd.scale = binary_scale;
  else
    wmnd.scale = metric_scale;
  active_interface = value("interface_name");
  if(!strcmp(active_interface, "%first"))
    active_interface = NULL;
  if(!strval_fe(psi_bool, value("use_long_names")))
    bit_set(CFG_SHORTNAME);
  if(strval_fe(psi_bool, value("show_max_values")))
    bit_set(CFG_SHOWMAX);
  if(!strval_fe(psi_bool, value("use_max_history")))
    bit_set(CFG_MAXSCREEN);
  if(strval_fe(psi_bool, value("display_time")))
    bit_set(CFG_SHOWTIME);
  wmnd.wavemode = waveval_fe(drwFuncs, value("wave_mode"));
  drv_name = value("driver");
  if(!strcmp(drv_name, "%auto"))
    drv_name = NULL;
  drv_interface = value("driver_interface");
  if(!strcmp(drv_interface, "%any"))
    drv_interface = NULL;
  if(strval_fe(psi_bool, value("quiet")))
    msg_messages = MSG_FERR;
  if(strval_fe(psi_bool, value("debug")))
    msg_messages |= MSG_FDBG;

  /* check for at least one display mode */
  if(!drwFuncs->funcName)
  {
    msg_err("no avaible display modes, exiting");
    exit(1);
  }

  /* initialize drivers and devices */
  if(!active_interface)
    active_interface = drv_interface;
  if(devices_init(drv_name, drv_interface))
  {
    msg_err("no drivers loaded, exiting");
    exit(1);
  }

  /* now it's safe to connect signals */
  signal(SIGINT, mainExit);
  signal(SIGTERM, mainExit);
  signal(SIGCHLD, reaper);
  sigemptyset(&masked);
  sigaddset(&masked, SIGTERM);
  sigaddset(&masked, SIGINT);

#ifdef USE_TREND
  /* initialize trend's data */
  signal(SIGPIPE, SIG_IGN);
  trendFd = (FILE**)calloc(sizeof(FILE*), wmnd.nr_devices * 2);
  trendUpd = strval_fe(psi_bool, value("trend_update"));
#endif

  msg_dbg(__POSITION__, "open X display");
  dispname = value("display");
  if(!(dockapp.d = XOpenDisplay(dispname)))
  {
    /* fprintf crashes on some systems if dispname is null */
    if(dispname)
      msg_err("unable to open display '%s'", dispname);
    else
      msg_err("unable to open default display");

    exit(-1);
  }
  new_window(win_name, "wmnd", 64, 64, argc, argv);

  add_mr(REG_DEV, 3, 3, 38, 9);		/* device */
  add_mr(REG_RT_PB, 54, 3, 7, 9);	/* up/down packet/byte mode */
  add_mr(REG_MAIN, 3, 22, 58, 31);	/* main display area */
  add_mr(REG_SCALE_RX, 3, 13, 29, 9);	/* scale meter, left side (rx) */
  add_mr(REG_SCALE_TX, 32, 13, 29, 9);	/* scale meter, right side (tx) */
  add_mr(REG_SCRIPT, 3, 54, 58, 7);	/* user script */

  /* updates should begin immediately */
  memset(&beat_time, 0, sizeof(beat_time));

  /* clear the number of remaining steps */
  wmnd.avgRSteps = 1;

  if(!active_interface)	/* set default device name */
    wmnd.curdev = devices;
  else
    devices_select(active_interface);

  draw_interface();

  msg_dbg(__POSITION__, "looping");
  XSync(dockapp.d, False); /* kick off X11 queue */

  /* loop forever */
  for(;;)
  {
    unsigned int j;
    sigset_t mask;
    struct timeval beat_ctime;
    unsigned long beat_gap;
    int gap;

    /* mask INT/TERM in get_stats */
    sigprocmask(SIG_BLOCK, &masked, &mask);

    /* get statistics for each existing device */
    for(ptr = devices; ptr; ptr = ptr->next)
    {
      devices_getstat(ptr, &ip, &op, &ib, &ob);

      /* check for device shutdown */
      if(ib < ptr->ib_stat_last)
	ptr->ib_stat_last = ib;
      if(ob < ptr->ob_stat_last)
	ptr->ob_stat_last = ob;
      if(ip < ptr->ip_stat_last)
	ptr->ip_stat_last = ip;
      if(op < ptr->op_stat_last)
	ptr->op_stat_last = op;

      /*
       * smoothing is performed before led setup and only on bytes, in order to
       * let leds blink even when the graph is smoothed. Smoothing packets has
       * no sense.
       */
      if(wmnd.smooth)
      {
	smooth(&ib, ptr->ib_stat_last, wmnd.smooth);
	smooth(&ob, ptr->ob_stat_last, wmnd.smooth);
      }

      /* setup leds */
      if(ptr == wmnd.curdev)
      {
	if(ptr->ip_stat_last == ip || ptr->online)
	  led_control(LED_RX, 0);
	else
	  led_control(LED_RX, 1);

	if(ptr->op_stat_last == op || ptr->online)
	  led_control(LED_TX, 0);
	else
	  led_control(LED_TX, 1);
      }

      /* save values in history */
      ptr->his[57][0] += ib - ptr->ib_stat_last;
      ptr->his[57][1] += ob - ptr->ob_stat_last;
      ptr->his[57][2] += ip - ptr->ip_stat_last;
      ptr->his[57][3] += op - ptr->op_stat_last;
      ptr->ib_max_his = MAX(ptr->ib_max_his, ptr->his[57][0]);
      ptr->ob_max_his = MAX(ptr->ob_max_his, ptr->his[57][1]);
      ptr->ip_max_his = MAX(ptr->ip_max_his, ptr->his[57][2]);
      ptr->op_max_his = MAX(ptr->op_max_his, ptr->his[57][3]);

      /* save lastest values */
      ptr->ib_stat_last = ib;
      ptr->ob_stat_last = ob;
      ptr->ip_stat_last = ip;
      ptr->op_stat_last = op;
    }

    /* restore mask */
    sigprocmask(SIG_SETMASK, &mask, NULL);

    /* fetch current time */
    gettimeofday(&beat_ctime, NULL);

    /* estimate the time gap in milliseconds */
    beat_gap = ((beat_ctime.tv_sec - beat_time.tv_sec) * 1000) +
      ((beat_ctime.tv_usec - beat_time.tv_usec) / 1000);
    gap = (wmnd.avgSteps != 1? wmnd.scroll: beat_gap / 100);

    if(beat_gap >= wmnd.scroll * 100)
    {
      beat_time = beat_ctime;

      /* drift the average stats */
      if(!--wmnd.avgRSteps)
      {
	float div = 1. / wmnd.avgSteps;

	for(ptr = devices; ptr; ptr = ptr->next)
	{
	  ptr->avg[0] = (unsigned long)(div * (ptr->ib_stat_last - ptr->avgBuf[0]));
	  ptr->avg[1] = (unsigned long)(div * (ptr->ob_stat_last - ptr->avgBuf[1]));
	  ptr->avg[2] = (unsigned long)(div * (ptr->ip_stat_last - ptr->avgBuf[2]));
	  ptr->avg[3] = (unsigned long)(div * (ptr->op_stat_last - ptr->avgBuf[3]));
	  ptr->avgBuf[0] = ptr->ib_stat_last;
	  ptr->avgBuf[1] = ptr->ob_stat_last;
	  ptr->avgBuf[2] = ptr->ip_stat_last;
	  ptr->avgBuf[3] = ptr->op_stat_last;
	}
	wmnd.avgRSteps = wmnd.avgSteps;
      }

      /* cause a beat_event to parse pending x requests */
      beat_event();

      /* scroll the statistics */
      draw_stats(wmnd.curdev, gap);
      for(ptr = devices; ptr; ptr = ptr->next)
      {
#ifdef USE_TREND
	if(trendUpd)
	{
	  int i, bp, idx;

	  for(bp = 0; bp != 2; ++bp)
	  {
	    idx = (bp? 0: 2);
	    i = trend_idx(ptr->devnum, bp);
	    if(trendFd[i])
	    {
	      if(check_trend(i))
		close_trend(i);
	      else
	      {
		fprintf(trendFd[i], "%lu %lu\n",
		    ptr->his[57][idx + 0], ptr->his[57][idx + 1]);
		fflush(trendFd[i]);
	      }
	    }
	  }
	}
#endif

	for(j = 1; j < 58; j++)
	{
	  ptr->his[j - 1][0] = ptr->his[j][0];
	  ptr->his[j - 1][1] = ptr->his[j][1];
	  ptr->his[j - 1][2] = ptr->his[j][2];
	  ptr->his[j - 1][3] = ptr->his[j][3];
	}
	memset(ptr->his[57], 0, sizeof(ptr->his[57]));
      }
    }

    /*
     * beat_event and redraw added also on expose and button release
     * events. Now the app is much responsive to user action
     */
    while (XPending(dockapp.d))
    {
      msg_dbg(__POSITION__, "X11 activity");
      XNextEvent(dockapp.d, &event);
      switch (event.type)
      {
      case Expose:
	dockapp.update = 1;
	break;
      case DestroyNotify:
	XCloseDisplay(dockapp.d);
	exit(0);
	break;
      case ButtonPress:
	btn = event.xbutton.button;
	rgn = check_mr(event.xbutton.x, event.xbutton.y);
	break;
      case ButtonRelease:
	if(btn == event.xbutton.button)
	{
	  if(rgn == check_mr(event.xbutton.x, event.xbutton.y))
	  {
	    click_event(rgn, btn);
	    beat_event();
	    draw_stats(wmnd.curdev, gap);
	  }
	}
	break;
      }
    }

    redraw_window();
    usleep(wmnd.refresh);
  }

#ifdef USE_TREND
  close_trends();
#endif
  devices_destroy();
  exit(0);
}


void
binary_scale(unsigned char sign, unsigned long value, char* buf)
{
  unsigned char scale;
  unsigned int i;
  char* r;

  if(value > 1073741823)
  {
    /* scale in giga */
    value = value >> 30;
    scale = 'G';
  }
  else
  if(value > 1048575)
  {
    /* scale in mega */
    value = value >> 20;
    scale = 'M';
  }
  else
  if(value > 1023)
  {
    /* scale in kilo */
    value = value >> 10;
    scale = 'K';
  }
  else
    scale = ' ';

  snprintf(buf, 7, "%c%lu", sign, value);
  r = buf;
  r++;

  for(i = 3; i > 0 && *r != '\0'; i--)
  {
    if(*r == '+' || *r == '-' || *r == '.')
      ++i;
    ++r;
  }
  *r++ = scale;
  *r = '\0';
}


void
metric_scale(unsigned char sign, unsigned long value, char* buf)
{
  float f;
  unsigned char scale;
  unsigned int i;
  char* r;

  f = (float) value;
  if(value > 999999999)
  {
    /* scale in giga */
    f /= 1000000000;
    scale = 'G';
  }
  else
  if(value > 999999)
  {
    /* scale in mega */
    f /= 1000000;
    scale = 'M';
  }
  else
  if(value > 999)
  {
    /* scale in kilo */
    f /= 1000;
    scale = 'K';
  }
  else
    scale = ' ';

  snprintf(buf, 7, "%c%f", sign, f);
  r = buf;
  r++;

  for(i = 3; i > 0 && *r != '\0'; i--)
  {
    if(*r == '+' || *r == '-' || *r == '.')
      ++i;
    ++r;
  }
  *r++ = scale;
  *r = '\0';
}


void
scale(char* rx_buf, char* tx_buf, unsigned long rx,
    unsigned long tx, const int gap)
{
  char rx_sign, tx_sign;

  if(rx > tx)
  {
    rx_sign = '+';
    tx_sign = '#';
  }
  else
  {
    rx_sign = '-';
    tx_sign = '*';
  }

  /* return the speed in bps */
  if(gap != 10)
  {
    float div = 10. / gap;
    tx = (unsigned long)(div * tx);
    rx = (unsigned long)(div * rx);
  }
  wmnd.scale(tx_sign, tx, tx_buf);
  wmnd.scale(rx_sign, rx, rx_buf);
}


void
draw_string(const char* buf, unsigned int x, unsigned int y)
{
  unsigned int w, sx = 0, sy = 0;
  unsigned int draw;
  const char* r;

  w = 3;
  draw = 0;
  for(r = buf; *r != '\0'; r++)
  {
    if(*r >= '0' && *r <= '9')
    {
      w = 5;
      sx = 1 + (w * (*r - '0'));
      sy = 86;
      draw = 1;
    }
    else
    if(*r == '.')
    {
      w = 2;
      sx = 62;
      sy = 86;
      draw = 1;
    }
    else
    if(*r >= 'A' && *r <= 'Z')
    {
      w = 5;
      sx = 1 + (w * (*r - 'A'));
      sy = 94;
      draw = 1;
    }
    else
    if(*r == '+')
    {
      w = 6;
      if(bit_get(CFG_MAXSCREEN))
      {
	sx = 133;
	sy = 94;
      }
      else
      {
	sx = 66;
	sy = 46;
      }
      draw = 1;
    }
    else
    if(*r == '-')
    {
      w = 6;
      if(bit_get(CFG_MAXSCREEN))
      {
	sx = 139;
	sy = 94;
      }
      else
      {
	sx = 72;
	sy = 46;
      }
      draw = 1;
    }
    else
    if(*r == '*')
    {
      w = 6;
      if(bit_get(CFG_MAXSCREEN))
      {
	sx = 145;
	sy = 94;
      }
      else
      {
	sx = 78;
	sy = 46;
      }
      draw = 1;
    }
    else
    if(*r == '#')
    {
      w = 6;
      if(bit_get(CFG_MAXSCREEN))
      {
	sx = 151;
	sy = 94;
      }
      else
      {
	sx = 84;
	sy = 46;
      }
      draw = 1;
    }
    if(draw)
    {
      copy_xpm_area(sx, sy, w, 7, x, y);
      draw = 0;
    }
    x += w;
  }
}


void
draw_rate(unsigned long rx, unsigned long tx, const int gap)
{
  char rx_buf[8];
  char tx_buf[8];

  /* clear rate bar */
  copy_xpm_area(100, 86, 58, 7, 3, 54);

  /* put rx/tx numbers into strings, scaling them */
  scale(rx_buf, tx_buf, rx, tx, gap);

  /* draw rx/tx strings */
  draw_string(rx_buf, 3, 54);
  draw_string(tx_buf, 32, 54);
}


void
draw_max(unsigned long rx, unsigned long tx)
{
  char rx_buf[16];
  char tx_buf[16];

  /* clear rate bar */
  copy_xpm_area(100, 86, 58, 7, 3, 11);

  /* put rx/tx numbers into strings, scaling them. Scale now acceps the median
   * time in microseconds to scale the values correctly; as we don't have the
   * median time for all samples, use a reasonable default */
  scale(rx_buf, tx_buf, rx, tx, wmnd.scroll);

  /* draw rx/tx strings */
  draw_string(rx_buf, 3, 11);
  draw_string(tx_buf, 32, 11);
}


void
draw_stats(struct Devices *ptr, const int gap)
{
  unsigned int k;
  unsigned long* p;
  unsigned int in, out;
  unsigned long rx_max_his, tx_max_his;
  unsigned long long rx_max, tx_max;
  unsigned int size;

  if(bit_get(CFG_SHOWMAX))
    size = 35; /* with max bar mode */
  else
    size = 41; /* without max bar */

  if(bit_get(CFG_MODE))
  {
    /* bytes mode */
    in = 0;
    out = 1;
    rx_max_his = ptr->ib_max_his;
    tx_max_his = ptr->ob_max_his;
  }
  else
  {
    /* packets mode */
    in = 2;
    out = 3;
    rx_max_his = ptr->ip_max_his;
    tx_max_his = ptr->op_max_his;
  }

  /* find maximum value in screen history */
  rx_max = tx_max = 0;
  p = (unsigned long*)ptr->his;
  for(k = 0; k < 58; k++)
  {
    rx_max = MAX(rx_max, p[in]);
    tx_max = MAX(tx_max, p[out]);
    p += 4;
  }

  /* draw rx/tx rate */
  p = ptr->avg;
  draw_rate(p[in], p[out], gap);

  if(bit_get(CFG_MAXSCREEN))
    draw_max(rx_max, tx_max);
  else
    draw_max(rx_max_his, tx_max_his);

  p = (unsigned long*)ptr->his;
  (*drwFuncs[wmnd.wavemode].funcPtr)(p, in, out, size, rx_max, tx_max);

  /* copy connection time over the graph */
  if(bit_get(CFG_SHOWTIME) && wmnd.curdev->devstart)
    copy_xpm_area(70, 36, 23, 7, 37, 46);
}


static void
led_control(const unsigned char led, const unsigned char mode)
{
  msg_dbg(__POSITION__, "led: %02x[%02x]", led, mode);
  switch (led)
  {
  case LED_POWER:
    switch (bit_get(CFG_MODE))
    {
    case 1:
      /* bytes */
      if(mode)
	/* on-line */
	copy_xpm_area(116, 65, 5, 7, 55, 4)
      else
	/* off-line */
	copy_xpm_area(122, 65, 5, 7, 55, 4);
      break;
    case 0:
      /* packets */
      if(mode)
	/* on-line */
	copy_xpm_area(128, 65, 5, 7, 55, 4)
      else
	/* off-line */
	copy_xpm_area(134, 65, 5, 7, 55, 4);
      break;
    }
    break;
  case LED_RX:
    if(mode)
    {
      /* turn on */
      if(bit_get(LED_RX))
      {
	msg_dbg(__POSITION__, "RX led already on");
	return;
      }
      copy_xpm_area(86, 70, 5, 4, 41, 4);
      bit_set(LED_RX);
      msg_dbg(__POSITION__, "RX led on");
    }
    else
    {
      /* turn off */
      if(!bit_get(LED_RX))
      {
	msg_dbg(__POSITION__, "RX led already off");
	return;
      }
      copy_xpm_area(92, 70, 5, 4, 41, 4);
      bit_off(LED_RX);
      msg_dbg(__POSITION__, "RX led off");
    }
    break;
  case LED_TX:
    if(mode)
    {
      /* turn on */
      if(bit_get(LED_TX))
      {
	msg_dbg(__POSITION__, "TX led already on");
	return;
      }
      copy_xpm_area(86, 65, 5, 4, 48, 4);
      bit_set(LED_TX);
      msg_dbg(__POSITION__, "TX led on");
    }
    else
    {
      /* turn off */
      if(!bit_get(LED_TX))
      {
	msg_dbg(__POSITION__, "TX led already off");
	return;
      }
      copy_xpm_area(92, 65, 5, 4, 48, 4);
      bit_off(LED_TX);
      msg_dbg(__POSITION__, "TX led off");
    }
    break;
  }
}


void
draw_interface(void)
{
  int i;
  int c;
  int k = 3;
  char temp[7];
  char* cur_name = wmnd.curdev->name;
  int cur_namelen = strlen(cur_name);

  /* refresh */
  copy_xpm_area(65, 54, 38, 9, 3, 3);
  led_control(LED_POWER, bit_get(RUN_ONLINE));

  /*
   * little modify to handle names shorter than 4 chars and minor bugfix
   * for names shorter than 6 chars (memory overwrite)
   */
  if(bit_get(CFG_SHORTNAME))
  {
    strncpy(temp, cur_name, (cur_namelen<4)?cur_namelen:3);
    temp[3] = (cur_namelen < 4)? '\0': cur_name[cur_namelen - 1];
    temp[4] = '\0';
  }
  else
  {
    if(cur_namelen > 6)
      cur_namelen = 6;
    strncpy(temp, cur_name, cur_namelen);
    temp[cur_namelen] = '\0';
  }

  for(i = 0; temp[i]; i++)
  {
    c = temp[i];
    if(c >= '0' && c <= '9') {
      c -= '0';
      copy_xpm_area(c * 6, 65, 6, 9, k, 3);
      k += 6;
    } else {
      if(c >= 'a' && c <= 'z')
	c = c - 'a' + 'A';
      c -= 'A';
      copy_xpm_area(c * 6, 75, 6, 9, k, 3);
      k += 6;
    }
  }
}


void
reaper(int sig)
{
  int dummy;
  wait(&dummy);
}


void
usage(void)
{
  int cnt = 0;

  fprintf(stderr,
      "wmnd - WindowMaker Network Devices %s\n\n"
      "usage:\n"
      "  -b                  base 2 scale (no fractions)\n"
      "  -c <color>          tx color\n"
      "  -C <color>          rx color\n"
      "  -L <color>          middle line color\n"
      "  -d <display name>\n"
      "  -g <geometry>\n"
      "  -h                  this help screen\n"
      "  -i <interface name> select this interface at startup\n"
      "  -l                  start using long device names\n"
      "  -m                  start with max values hidden\n"
      "  -M                  use max values from the entire history\n"
      "  -f <config>         read config instead of ~/.wmndrc\n"
      "  -F                  don't parse ~/.wmndrc\n"
      "  -r <rate>           refresh rate in microseconds\n"
      "  -s <scroll>         scroll rate in tenth of seconds\n"
      "  -S <samples>        number of samples to average for the speed indicator\n"
      "  -t                  start without displaying time\n"
      "  -n <name>           Use <name> instead of 'wmnd' for the window name\n"
      "  -v                  print the version number\n"
      "  -q                  be less verbose\n"
      "  -Q                  enable verbosity\n"
      "  -o <float>          smoothing factor (0-1)\n"
      "  -a <bps>            fixed max scale at <bps>\n"
      "  -w <mode>           select display mode (see below)\n"
      "  -D <driver>         specify a driver to use\n"
      "  -I <interface name> tell to driver/s the interface to monitor\n\n"
      "builtin drivers: ", WMND_VERSION);

  /* display builtin drivers */
  while(drivers_table[cnt].driver_name)
  {
    if(drivers_table[cnt+1].driver_name)
      fprintf(stderr, "%s, ", drivers_table[cnt].driver_name);
    else
      fprintf(stderr, "%s.\n", drivers_table[cnt].driver_name);
    cnt++;
  }

  /* display builtin display modes */
  fprintf(stderr, "builtin display modes: ");
  cnt = 0;
  while(drwFuncs[cnt].funcName)
  {
    if(drwFuncs[cnt + 1].funcName)
      fprintf(stderr,"%s, ",drwFuncs[cnt].funcName);
    else
      fprintf(stderr,"%s.\n",drwFuncs[cnt].funcName);
    cnt++;
  }
}


void
printversion(void)
{
  printf("%s\n", WMND_VERSION);
}


int
devices_init(const char* driver, const char* interface)
{
  /*
   * devices init, initializes all drivers/devices
   * return: 0 on success, 1 on error
   */

  int cnt = -1;
  int devnum;
  int in_loop0;
  struct Devices *prt;
  struct Devices *aftPrt;
  struct Devices *tmpPrt;
  wmnd.nr_devices = 0;

  /* creating and empy first device */
  msg_dbg(__POSITION__, "initializing devices");

  devices = (struct Devices*)malloc(sizeof(struct Devices));
  devices->next = NULL;
  prt = devices;

  /* starting all drivers */
  do
  {
    cnt++;

    /* check if driver is specified and check devicename */
    if(driver && strcmp(driver, drivers_table[cnt].driver_name))
      continue;

    /* device matches or driver is NULL, then check if driver is avaible */
    msg_dbg(__POSITION__, "probing %s", drivers_table[cnt].driver_name);
    aftPrt = prt;
    devnum = (*drivers_table[cnt].list_devices)(interface, prt);
    drivers_table[cnt].status = devnum;
    if(!devnum)
      continue;

    /* driver is avaible, set up all driver's devices */
    for(in_loop0 = 0; in_loop0 < devnum; in_loop0++)
    {
      prt = prt->next;
      prt->devnum = wmnd.nr_devices + in_loop0;
      prt->drvnum = cnt; /* set the driver number */
      if(drivers_table[cnt].init_device &&
	  (*drivers_table[cnt].init_device)(prt) == 1) /* init the device */
      {
	msg_err("failed to initialize device %s,%d",
	    drivers_table[cnt].driver_name, in_loop0);
	devnum--;

	/* last element in list */
	if(in_loop0 == devnum-1 || !devnum)
	{
	  free(prt);
	  prt = aftPrt;
	  continue;
	}

	/* remove device from list */
	tmpPrt = prt->next;
	free(prt);
	prt = tmpPrt;

	if(in_loop0 == devnum) continue;
	aftPrt = prt;
	in_loop0--;
      }
      else
      {
	/* initialize statistics for this device */
	int cnt;
	unsigned long int ib, ob, ip, op;

	/* sample some stats to inizialize cleanly the graph */
	(*drivers_table[prt->drvnum].get_stats)(prt, &ip, &op, &ib, &ob);

	prt->ib_max_his = prt->ob_max_his =
	  prt->ip_max_his = prt->op_max_his = 0;
	prt->ib_stat_last = ib;
	prt->ob_stat_last = ob;
	prt->ip_stat_last = ip;
	prt->op_stat_last = op;

	/* cleaning history */
	for(cnt = 0; cnt < 58; cnt++)
	  memset(prt->his, 0, sizeof(prt->his));

	/* clean average sampling buffers */
	prt->avgBuf[0] = ib;
	prt->avgBuf[1] = ob;
	prt->avgBuf[2] = ip;
	prt->avgBuf[3] = op;
      }
    }

    /* all new devices are ok */
    wmnd.nr_devices += devnum;
  }
  while(drivers_table[cnt+1].driver_name);

  /* remove the first null element */
  prt = devices;
  devices = prt->next;
  free(prt);

  return (!wmnd.nr_devices);
}


void
devices_prev(void)
{
  /*
   * devices_prev: cycles for the previous device.
   */

  struct Devices* tmp = devices;

  while(tmp->next && (wmnd.curdev == devices || tmp->next != wmnd.curdev))
    tmp = tmp->next;

  wmnd.curdev = tmp;
}


void
devices_select(const char* interface)
{
  /*
   * devices_select: sets wmnd.curdev looking up a name
   * if name is null, cycles for next device
   */

  struct Devices* prt = devices;

  if(interface)
  {
    /* searching for a device */
    wmnd.curdev = devices;
    while(prt)
    {
      if(!strcmp(interface, prt->name))
      {
	/* name matches */
	wmnd.curdev = prt;
	break;
      }
      prt = prt->next;
    }
  }
  else
  {
    /* switching active device */
    if(!wmnd.curdev->next)
      wmnd.curdev = devices; /* last element, back to first */
    else
      wmnd.curdev = wmnd.curdev->next;
  }
}


void
devices_getstat(struct Devices *device, unsigned long* ip, unsigned long* op,
    unsigned long* ib, unsigned long* ob)
{
  /*
   * devices_getstat: run appropriate get_stats for device
   */

  int online =
    (*drivers_table[device->drvnum].get_stats)(device, ip, op, ib, ob);
  if(!online && device->online)
  {
    /* the device retuned back online, update the timestamp */
    time(&device->devstart);
  }
  else
  if(online && !device->online)
  {
    /* device shutdown */
    device->devstart = 0;
  }

  device->online = online;
}


void
devices_destroy(void)
{
  /*
   * devices_destroy: sends destroy signal to all devices and frees devices
   */

  struct Devices *ptr;
  int cnt;

  while(devices->next)
  {
    ptr = devices;
    devices = devices->next;
    if(drivers_table[ptr->drvnum].terminate_device)
      (*drivers_table[ptr->drvnum].terminate_device)(ptr);
    free(ptr->name);
    free(ptr);
  }

  for(cnt = 0; drivers_table[cnt].driver_name; ++cnt)
  {
    if(drivers_table[cnt].status && drivers_table[cnt].terminate_driver)
      (*drivers_table[cnt].terminate_driver)();
  }
}
