/* GKRELLMWireless
|  Copyright (C) 2000-2002 Sjoerd Simons
|
|  Author:  Sjoerd Simons sjoerd@gkrellm.luon.net
|  Latest versions might be found at:  http://gkrellm.luon.net
|
|  This program is free software which I release under the GNU General Public
|  License. You may redistribute and/or modify this program under the terms
|  of that license as published by the Free Software Foundation; either
|  version 2 of the License, or (at your option) any later version.
|
|  This program is distributed in the hope that it will be useful,
|  but WITHOUT ANY WARRANTY; without even the implied warranty of
|  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
|  GNU General Public License for more details.
|
|  To get a copy of the GNU General Puplic License,  write to the
|  Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "wireless.h"

static GkrellmMonitor *monitor;
static gint panel_style_id;
static wcard_t *cards = NULL;
static GtkWidget *PanelBox;

static void reset_panel(int create);

wcard_t *
new_wcard(gchar *interface,int found, int flags) {
   /* creates an new wcard entry for interface, and sets the flags if it's not
   a newly found card */
   wcard_t *newcard;
   wcard_t *card;

   newcard = malloc(sizeof(wcard_t));
   newcard->next = NULL;
   newcard->ifname = strdup(interface);
   newcard->level_panel = NULL;
   newcard->link_panel = NULL;
   newcard->noise_panel = NULL;
   newcard->bitrate_panel = NULL;

   if (found) newcard->flags = (0 | SHOW | SHOW_LINK | SHOW_LEVEL | SHOW_NOISE | SHOW_BITRATE);
   else  /* a card which is not detected, but from a config file */
     newcard->flags = flags & ~ACTIVE;

   if (cards == NULL) cards = newcard;
   else {
     for (card = cards ; card->next != NULL; card = card->next);
     card->next  = newcard;
   }
   return newcard;
}

void 
del_wcard(wcard_t *card) {
  /* called when a card isn't active anymore, sets it's entry to */
  /* uninitialized */
  card->flags &= (~ACTIVE);
  reset_panel(0);
}

wcard_t *found_wcard(gchar *interface) {
  /*  called by the system specific find_wcard functions */
  /* for each wireless card it had found */
  /* adds/modifies an wireless card entry for it */
  /* returns the entry, so it's system specific entry can be edited */
  wcard_t *card;

  for (card = cards ; card != NULL ; card = card->next) {
    if (strcmp(card->ifname,interface)) {
      /* wrong entry */
      continue;
    } 
    if (card->flags & ACTIVE) {
      /* already showing this one */
      return NULL;
    } else {
      card->flags |= ACTIVE;
      return card;
    }
    /* found the entry */
  }
  /* never saw this card before, creating new entry */
  card = new_wcard(interface,1,0);
  card->flags |= ACTIVE;
  gkrellm_config_modified();
  return card;
}

/* system specific stuff */
#if defined(__FreeBSD__) || defined(__NetBSD__)
/* FreeBSD & NetBSD specific */

static int
find_wi_card(void) {
  /* possible interfaces */
  char interfaces[][4] = {"wi0","wi1","wi2"};

  /* wireless info request struct */
  struct wi_req wreq;
  /* interface request struct */
  struct ifreq ifr;
  int s,i,ret;

  ret = FALSE;
  /* open a socket for ioctl's */
  if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) return ret; 

  /* clean out the wreq struct */ 
  memset(&wreq, 0, sizeof(wreq));

  wreq.wi_len = WI_MAX_DATALEN;
  /* we want to know the quality */
  wreq.wi_type = WI_RID_COMMS_QUALITY;

  for (i = 0 ; i < 3 ; i++ ) {
    memset(&ifr,0, sizeof(ifr));
    /* ask information about a certain interface */
    strcpy(ifr.ifr_name,interfaces[i]);

    ifr.ifr_data = (caddr_t)&wreq;

    /* get information about this interface, if it excists it's an card */
    if (ioctl(s, SIOCGWAVELAN, &ifr) == -1) continue; 
     
    if(found_wcard(interfaces[i]) != NULL) ret = TRUE;
  }

  close(s);
  return ret;
}

#if !defined(__NetBSD__)
static gint
find_an_card(void) {
  char interfaces[][4] = {"an0","an1","an2"};
  int i,s,ret ;
  struct ifreq ifr;
  struct an_req areq;
  
  ret = FALSE;
  /* open a socket for ioctl's */
  if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) return ret; 

  for (i = 0 ; i < 3 ; i++ ) {
    memset(&ifr,0, sizeof(ifr));
    /* ask information about a certain interface */
    strcpy(ifr.ifr_name,interfaces[i]);

    areq.an_len = AN_MAX_DATALEN;
    areq.an_type = AN_RID_READ_CACHE;

    ifr.ifr_data = (caddr_t)&areq;

    if (ioctl(s,SIOCGAIRONET,&ifr) == -1) continue;
    if(found_wcard(interfaces[i]) != NULL) ret = TRUE;
  }
  close(s);
  return ret;
}
#endif /* !defined(__NetBSD__) */

static gint 
find_wlancard(void) {
  gint ret = FALSE;
 
  ret = find_wi_card();
#if !defined(__NetBSD__)
  ret = find_an_card() || ret;
#endif /* !defined(__NetBSD__) */
  return ret;
}

static int
get_wi_link_quality(wcard_t *card, float *quality, float *level, float *noise) {
  /* wireless info request struct */
  struct wi_req wreq;
  /* interface request struct */
  struct ifreq ifr;
  int s;

  /* open a socket for ioctl's */
  if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) return FALSE; 

  /* clean out the wreq struct */ 
  memset(&wreq, 0, sizeof(wreq));

  wreq.wi_len = WI_MAX_DATALEN;
  /* we want to know the quality */
  wreq.wi_type = WI_RID_COMMS_QUALITY;

  memset(&ifr,0, sizeof(ifr));
  /* ask information about a certain interface */
 strcpy(ifr.ifr_name,card->ifname);

 ifr.ifr_data = (caddr_t)&wreq;

 /* get information about this interface */
 if (ioctl(s, SIOCGWAVELAN, &ifr) == -1) {
   close(s);
   return FALSE; 
 }

 close(s);
 *quality = wreq.wi_val[0];
 *level = wreq.wi_val[1];
 *noise = wreq.wi_val[2];

 return TRUE;
}

#if !defined(__NetBSD__)
static int
get_an_link_quality(wcard_t *card, float *quality, float *level, float *noise) {
  int nr,s;
  struct ifreq ifr;
  struct an_req areq;
  struct an_sigcache *sc;
  
  /* open a socket for ioctl's */
  if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) return FALSE; 

  memset(&ifr,0, sizeof(ifr));
  memset(&areq,0, sizeof(areq));
  /* ask information about a certain interface */
  strcpy(ifr.ifr_name,card->ifname);

  areq.an_len = AN_MAX_DATALEN;
  areq.an_type = AN_RID_READ_CACHE;

  ifr.ifr_data = (caddr_t)&areq;

  if (ioctl(s,SIOCGAIRONET,&ifr) == -1) {
    close(s);
    return FALSE;
  }
  close(s);

  /* anval excists out of a integer which represents the number of sigcaches*/
  /* followed by the sigcaches */
  nr = (int) *areq.an_val; /* number of signal caches */ 

  if (nr == 0) return FALSE; 

  /* we use the first sigcache for statistics, which seems to work ok for me */
  sc = (struct an_sigcache *) ((char *) &areq.an_val + sizeof(int));

  *quality = sc->quality;
  *level = sc->signal;
  *noise = sc->noise;
  return TRUE;
}
#endif /* !defined(__NetBSD__) */

static int
get_link_quality(wcard_t *card, float *quality, float *level, float *noise) {
  switch (card->ifname[0]) { 
#if !defined(__NetBSD__)
    case 'a': /* an card */
      return get_an_link_quality(card,quality,level,noise);
#endif /* !defined(__NetBSD__) */
    case 'w': /* wi card */
      return get_wi_link_quality(card,quality,level,noise);
  }
  return FALSE;
}

static int
get_bitrate(wcard_t *card, int *maxbitrate, int *bitrate) {
  *maxbitrate = 0;
  *bitrate = 0;
  return FALSE;
}
#endif

#ifdef __linux__
/* Linux Specific*/

#define WIRELESS "/proc/net/wireless"
static gint
find_wlancard(void) {
  FILE *procfile;
  int ret = FALSE;
  char iface[5],procread[256], *c;
  
  if ((procfile = fopen(WIRELESS,"r")) == NULL) return FALSE;

  /* 2 lines header */
  fgets(procread,sizeof(procread),procfile);
  fgets(procread,sizeof(procread),procfile);

  while(fgets(procread,sizeof(procread),procfile) != NULL) {
    sscanf(procread,"%s: %*s %*f %*f %*f %*d %*d %*d",
           iface);
    c = strstr(iface,":");
    *c = '\0';
    if(found_wcard(iface) != NULL) ret = TRUE;
  }
  fclose(procfile);
  
  return ret;
}
 
float get_next_fl(char **string) {
  char *c;
  float ret;
  c = *string;
  while (!isdigit(*c) && *c != '\0') c++;
  ret = atof(*string);
  while(!isspace(*c) && *c != '\0') c++;
  *string = c;
  return ret;
}


static int
get_link_quality(wcard_t *card, float *link, float *level, float *noise) {
  FILE *procfile;
  char procread[256], *c;

  if ((procfile = fopen(WIRELESS,"r")) != NULL) {
    /* 2 lines header */
    fgets(procread,sizeof(procread),procfile);
    fgets(procread,sizeof(procread),procfile);

    /* search for our card */
    while (fgets(procread,sizeof(procread),procfile) != NULL) {
      c = procread;
      while (isspace(*c)) c++;
      if (!strncmp(c,card->ifname,strlen(card->ifname))) {
        while (!isspace(*c) && *c != '\0') c++;
                 get_next_fl(&c); /* status thingie */
         *link = get_next_fl(&c);
        *level = get_next_fl(&c) - 256;
        *noise = get_next_fl(&c) -256;
      	fclose(procfile);
	      return TRUE;
      }
    }
    fclose(procfile);
  }
  del_wcard(card);
  return FALSE;
}

/* the next three functions (iw_sockets_open, iw_get_ext,
   iw_get_range_info) were copied and modified from iwlib.c which is
   part of the wireless-tools package by Jean Tourrilhes */

static int
iw_sockets_open(void) {
  static const int families[] = {
    AF_INET, AF_IPX, AF_AX25, AF_APPLETALK
  };
  unsigned int i;
  int sock;
  
  for(i = 0; i < (sizeof(families) / sizeof(int)); ++i) {
    sock = socket(families[i], SOCK_DGRAM, 0);
    if(sock >= 0)
      return sock;
  }
  
  return -1;
}

static inline int
iw_get_ext(int skfd, char *ifname, int request, struct iwreq *pwrq) {
  strncpy(pwrq->ifr_name, ifname, IFNAMSIZ);
  return ioctl(skfd, request, pwrq);
}

static int
iw_get_range_info(int	skfd, char *ifname, struct iw_range *range) {
  struct iwreq wrq;
  /* ask for the range info, but get it in a buffer that's much larger
     than necessary, because the wireless stuff maintains almost no
     binary-level backward compatibility, so the data passed back
     could actually be larger than expected, in a different format
     than expected :-/ */
  char buffer[sizeof(struct iw_range) * 2];
  memset(buffer, 0, sizeof(buffer));
  wrq.u.data.pointer = (caddr_t)buffer;
  wrq.u.data.length = sizeof(buffer);
  wrq.u.data.flags = 0;
  if(iw_get_ext(skfd, ifname, SIOCGIWRANGE, &wrq) < 0)
    return -1;
  memcpy((char *)range, buffer, sizeof(struct iw_range));
  return 0;
}

static int
get_bitrate(wcard_t *card, int *maxbitrate, int *bitrate) {
  int skfd;
  struct iw_range range;
  struct iwreq wrq;
  int i;
  
  /* open a socket, any socket */
  if((skfd = iw_sockets_open()) < 0)
    return FALSE;
  
  /* get bitrate range info */
  if((iw_get_ext(skfd, card->ifname, SIOCGIWRATE, &wrq) < 0)
     || (iw_get_range_info(skfd, card->ifname, &range) < 0)) {
    close(skfd);
    return FALSE;
  }
  
  close(skfd);
  
  /* check that there are a reasonable number of bitrates */
  if((range.num_bitrates <= 0) || (range.num_bitrates > IW_MAX_BITRATES))
    return FALSE;
  
  /* figure out the current bitrate and the largest bitrate */
  *bitrate = wrq.u.bitrate.value;
  *maxbitrate = range.bitrate[0];
  for(i = 1; i < range.num_bitrates; ++i)
    if(*maxbitrate < range.bitrate[i])
      *maxbitrate = range.bitrate[i];
  
  return TRUE;
}
#endif /* __linux__ */

/* end of system specific code */
static gint
panel_expose_event(GtkWidget *widget, GdkEventExpose *event, GkrellmPanel *panel) {
  gdk_draw_pixmap(widget->window,
    widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
    panel->pixmap, event->area.x, event->area.y, event->area.x, event->area.y,
    event->area.width, event->area.height);

  return FALSE;
}

static void 
del_panel(GkrellmPanel *panel) {
  if (panel == NULL) return;
  gkrellm_destroy_decal_list(panel);
  gkrellm_destroy_krell_list(panel);
  gkrellm_panel_destroy(panel);
  gkrellm_pack_side_frames();
}

static void
create_panel(GkrellmPanel **rpanel, GkrellmDecal **text, int fullscale,int create) {
  GkrellmKrell *k;
  GkrellmStyle *style;
  GkrellmPiximage  *krell_image;
  int first_create = 0 ;
  GkrellmPanel *panel = *rpanel;

  if (panel == NULL) {
      panel = gkrellm_panel_new0();
      first_create = 1;
  } else {
    gkrellm_destroy_decal_list(panel);
    gkrellm_destroy_krell_list(panel);
  }
  style = gkrellm_meter_style(panel_style_id);

  krell_image = gkrellm_krell_meter_piximage(panel_style_id);
  k = gkrellm_create_krell(panel, krell_image, style);
  gkrellm_set_krell_full_scale(k, fullscale, 1);

  panel->textstyle = gkrellm_meter_textstyle(panel_style_id);

  *text = gkrellm_create_decal_text(panel,"0"
                                          ,panel->textstyle,style,-1,-1,-1);
  gkrellm_panel_configure(panel,NULL,style);
  gkrellm_panel_create(PanelBox, monitor, panel); 

  if (first_create || create) {
      g_signal_connect(GTK_OBJECT(panel->drawing_area), "expose_event",
                         G_CALLBACK(panel_expose_event),panel);
  }

  gkrellm_draw_decal_text(panel,*text,"wireless",-10);
  gkrellm_draw_panel_layers(panel);

  gkrellm_pack_side_frames();

  *rpanel = panel;
}

static void
reset_panel(int create) {
  int bitrate, mbitrate;
  /* (re)create all the needed panels */
  /* if create then gkrellm is newly build (theme switch or whatever) */
  /* else it's called by the plugin itself (new card, changed config) */
  wcard_t *card;
  for (card = cards ; card != NULL ; card = card->next ) {
    if (!(card->flags & ACTIVE) || !(card->flags & SHOW)) {
      del_panel(card->level_panel); card->level_panel = NULL;
      del_panel(card->link_panel); card->link_panel = NULL;
      del_panel(card->noise_panel); card->noise_panel = NULL;
      del_panel(card->bitrate_panel); card->bitrate_panel = NULL;
      continue;
    }
    if (card->flags & SHOW_LINK)
      create_panel(&(card->link_panel),&(card->link_text),LINKQ_MAX,create);
    else { del_panel(card->link_panel); card->link_panel = NULL; }

    if (card->flags & SHOW_LEVEL)
      create_panel(&(card->level_panel),&(card->level_text),LEVEL_MAX,create);
    else { del_panel(card->level_panel); card->level_panel = NULL; }

    if (card->flags & SHOW_NOISE)
      create_panel(&(card->noise_panel),&(card->noise_text),NOISE_MAX,create);
    else { del_panel(card->noise_panel); card->noise_panel = NULL; }

    if (card->flags & SHOW_BITRATE) {
      if (get_bitrate(card,&mbitrate,&bitrate)) { 
        create_panel(&(card->bitrate_panel),&(card->bitrate_text),
                     mbitrate,create);
      } else {
        create_panel(&(card->bitrate_panel),&(card->bitrate_text),
                     BITRATE_MAX,create);
      }
    }
    else { del_panel(card->bitrate_panel); card->bitrate_panel = NULL; }
  }
}

static void
update_panel(GkrellmPanel *panel, GkrellmDecal *decal,
             char *ifname, char *amount_text, float amount) {
  GkrellmKrell *k;
  char text[50];

  if (panel == NULL) return;

  if(ifname != NULL)
    snprintf(text, sizeof(text), "%s: %s", ifname, amount_text);
  else
    strncpy(text, amount_text, sizeof(text));
  k = KRELL(panel);
  k->previous = 0;
  gkrellm_update_krell(panel,k,abs(amount));
  gkrellm_draw_decal_text(panel,decal,text,amount);
  gkrellm_draw_panel_layers(panel);
}

static void
update_normal_panel(GkrellmPanel *panel, GkrellmDecal *decal,
                    char *header, char *ifname, float amount) {
  char amount_text[50];
  snprintf(amount_text, sizeof(amount_text), "%.0f %s", amount, header);
  update_panel(panel, decal, ifname, amount_text, amount);
}

static void
update_bitrate_panel(GkrellmPanel *panel, GkrellmDecal *decal,
                     char *ifname, int maxbitrate, int bitrate) {
  char amount_text[50];
  float amount;

  if(bitrate > 1e9)
    snprintf(amount_text, sizeof(amount_text), "%.0f Gb/s", (bitrate / 1e9));
  else if(bitrate > 1e6)
    snprintf(amount_text, sizeof(amount_text), "%.0f Mb/s", (bitrate / 1e6));
  else
    snprintf(amount_text, sizeof(amount_text), "%.0f Kb/s", (bitrate / 1e3));
  amount = (bitrate / (float)maxbitrate);
  update_panel(panel, decal, ifname, amount_text, amount);
}

static void
update_plugin(void) {
  wcard_t *card;
  float link = 0, level = 0, noise = 0;
  int maxbitrate = 0, bitrate = 0;
  char *ifname;

  if (GK.second_tick) {
    for (card = cards;  card != NULL; card = card->next) {
      if (!(card->flags & ACTIVE) || !(card->flags & SHOW)) continue;
      ifname = ((card->flags & HIDE_NAME)?NULL:card->ifname);
      /* get_quality_info: get info in a system dependant way */  
      if (get_link_quality(card,&link,&level,&noise)) {
        update_normal_panel(card->level_panel,card->level_text
                     ,"Level",ifname,level);
        update_normal_panel(card->link_panel,card->link_text
                     ,"Link",ifname,link);
        update_normal_panel(card->noise_panel,card->noise_text
                     ,"Noise",ifname,noise);
      }
      if (get_bitrate(card,&maxbitrate,&bitrate)) {
        update_bitrate_panel(card->bitrate_panel,card->bitrate_text,
                           ifname,maxbitrate,bitrate);
      }
    }
  } 
  if (GK.two_second_tick) {
    if (find_wlancard() == TRUE) { 
      reset_panel(0);
    }
  }
}

static void
create_plugin(GtkWidget *vbox, gint first_create) {
  if (first_create) {
    PanelBox = vbox;
    /* first time it's created, only save panelbox here */
  } else {
    reset_panel(1); /* rebuild the panels */
  }
}

static void
save_plugin_config (FILE *f) {
  wcard_t *card;
  
  for (card = cards; card != NULL ; card = card->next) {
    fprintf(f,"%s %s %d\n",PLUGIN_CONFIG_KEYWORD,card->ifname,card->flags);
  }
}

static void
load_plugin_config(gchar *arg) {
  gchar ifname[10];
  int flags;
  if (sscanf(arg,"%s %d\n",ifname,&flags) == 2) {
    new_wcard(ifname,0,flags);
  }
}

/* the config panel stuff and callbacks */
static void
button_toggled(GtkWidget *widget, wcard_t *card, int value) {
   if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
     card->cflags |= value;
   else 
     card->cflags &= (~value);  
}

static void
cb_show_button_toggled(GtkWidget *widget, wcard_t *card) {
  button_toggled(widget,card,SHOW);
}

static void
cb_name_button_toggled(GtkWidget *widget, wcard_t *card) {
  button_toggled(widget,card,HIDE_NAME);
}


static void
cb_link_button_toggled(GtkWidget *widget, wcard_t *card) {
  button_toggled(widget,card,SHOW_LINK);
}


static void
cb_level_button_toggled(GtkWidget *widget, wcard_t *card) {
  button_toggled(widget,card,SHOW_LEVEL);
}

static void
cb_noise_button_toggled(GtkWidget *widget, wcard_t *card) {
  button_toggled(widget,card,SHOW_NOISE);
}

static void
cb_bitrate_button_toggled(GtkWidget *widget, wcard_t *card) {
  button_toggled(widget,card,SHOW_BITRATE);
}

static void
create_toggle_button(char *name,int active, wcard_t *card,
                     GtkWidget *box, GtkSignalFunc func) {

  GtkWidget *button;

  button = gtk_check_button_new_with_label(name);
  gtk_box_pack_start(GTK_BOX(box),button,FALSE,TRUE,3);
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),active);
  g_signal_connect(G_OBJECT(button),"toggled",
      G_CALLBACK(func),card);
}

static void
create_device_tab(GtkWidget *notebook,wcard_t *card) {
  GtkWidget *label;
  GtkWidget *frame;
  GtkWidget *vbox;
  GtkWidget *separator;

  label = gtk_label_new(card->ifname);
  frame = gtk_frame_new(NULL);

  gtk_notebook_append_page(GTK_NOTEBOOK(notebook),frame,label);

  vbox = gtk_vbox_new(FALSE,0);
  gtk_container_add(GTK_CONTAINER(frame),vbox);

  create_toggle_button("Show this interface" ,card->flags & SHOW, card, 
                         vbox, (GtkSignalFunc) cb_show_button_toggled);
  create_toggle_button("Hide interface name" ,card->flags & HIDE_NAME, card,
                         vbox, (GtkSignalFunc) cb_name_button_toggled);
  
  separator = gtk_hseparator_new();
  gtk_box_pack_start(GTK_BOX(vbox),separator,FALSE,TRUE,3);

  create_toggle_button("Show link quality" ,card->flags & SHOW_LINK, card, 
                         vbox, (GtkSignalFunc) cb_link_button_toggled);
  create_toggle_button("Show signal level" ,card->flags & SHOW_LEVEL, card, 
                         vbox, (GtkSignalFunc) cb_level_button_toggled);
  create_toggle_button("Show noise level" ,card->flags & SHOW_NOISE, card, 
                         vbox, (GtkSignalFunc) cb_noise_button_toggled);
#ifdef __linux__
  create_toggle_button("Show bit rate" ,card->flags & SHOW_BITRATE, card, 
                         vbox, (GtkSignalFunc) cb_bitrate_button_toggled);
#endif /* __linux__ */
}

static void
create_devices_tab(GtkWidget *notebook) {
  wcard_t *card;
  
  for (card = cards ; card != NULL; card = card->next) {
    create_device_tab(notebook, card); 
    card->cflags = card->flags;
  }
}

static void 
create_help_text(GtkWidget *text) {
  gchar *info_text[] = 
  {
    "<b>This plugin allows you to monitor the quality of a wireless lan card\n\n",
    "<b>Configuration:\n",
    "Every detected wireless interface will have ",
    "one config tab with the following options:\n",
    "<b>\tShow this interface:\n",
    "\tShow information about the interface\n",
    "<b>\tHide interface name:\n",
    "\tHide interface name (e.g., eth1) from information\n",
    "<b>\tShow link quality:\n",
    "\tShow the link quality of this interface\n",
    "<b>\tShow signal level:\n",
    "\tShow the signal level of this interface\n",
    "<b>\tShow noise level:\n", 
    "\tShow the noise level of this interface\n",
    "<b>\tShow bit rate:\n", 
    "\tShow the bit rate of this interface\n"
  };
  gkrellm_gtk_text_view_append_strings(text,info_text,
      sizeof(info_text)/sizeof(gchar *));
}

static void
create_info_tab(GtkWidget *notebook) {
  GtkWidget *frame;
  GtkWidget *scrolled;
  GtkWidget *text;
  GtkWidget *page;

  frame = gtk_frame_new(NULL);
  gtk_container_border_width(GTK_CONTAINER(frame),3);
  scrolled = gtk_scrolled_window_new(NULL,NULL);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
        GTK_POLICY_AUTOMATIC,GTK_POLICY_AUTOMATIC);
  
  gtk_container_add(GTK_CONTAINER(frame),scrolled);

  page = gkrellm_gtk_notebook_page(notebook,"Info");
  text = gkrellm_gtk_scrolled_text_view(page,NULL,
                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
  create_help_text(text);
}

static void
create_about_tab(GtkWidget *notebook) {
gchar *plugin_about_text;
GtkWidget *text;
GtkWidget *label;

  plugin_about_text = g_strdup_printf(
     "GkrellMWireless %d.%d%s\n" \
     "GKrellM Wireless Plugin\n\n" \
     "Copyright (C) 2000-2001 Sjoerd Simons\n" \
     "sjoerd@luon.net\n" \
     "http://gkrellm.luon.net \n\n" \
     "Released under the GNU Public Licence",
     WIRELESS_MAJOR_VERSION,WIRELESS_MINOR_VERSION,WIRELESS_EXTRA_VERSION);

 text = gtk_label_new(plugin_about_text); 
 label = gtk_label_new("About");
 gtk_notebook_append_page(GTK_NOTEBOOK(notebook),text,label);
 g_free(plugin_about_text);
}

static void
create_plugin_config(GtkWidget *tab) {
  GtkWidget *notebook;

  notebook = gtk_notebook_new();
  gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook),GTK_POS_TOP);
  gtk_box_pack_start(GTK_BOX(tab),notebook,TRUE,TRUE,0);

  create_devices_tab(notebook);
  create_info_tab(notebook);
  create_about_tab(notebook);
}

static void
apply_plugin_config(void) {
  wcard_t *card;

  for (card = cards ; card != NULL ; card = card->next) {
    card->flags = card->cflags;
  }
  reset_panel(0);
}
/* end of config panel stuff */

static GkrellmMonitor  wireless_meter  = {
  "Wireless",      /* Name, for config tab.    */
  0,          /* Id,  0 if a plugin       */
  create_plugin,    /* The create function      */
  update_plugin,    /* The update function      */
  create_plugin_config,  /* The config tab create function   */
  apply_plugin_config, /* Apply the config function        */

  save_plugin_config,  /* Save user config      */
  load_plugin_config,  /* Load user config      */
  PLUGIN_CONFIG_KEYWORD,  /* config keyword      */

  NULL,        /* Undefined 2  */
  NULL,        /* Undefined 1  */
  NULL,        /* Undefined 0  */

  MON_FS,      /* Insert plugin before this monitor.  Choose  */
            /*   MON_CLOCK, MON_CPU, MON_PROC, MON_DISK,  */
            /*   MON_INET, MON_NET, MON_FS, MON_MAIL,    */
            /*   MON_APM, or MON_UPTIME            */
            /* Choose wisely, others have to live with it.  */

  NULL,        /* Handle if a plugin, filled in by GKrellM     */
  NULL        /* path if a plugin, filled in by GKrellM       */
};


  /* All GKrellM plugins must have one global routine named init_plugin()
  |  which returns a pointer to a filled in monitor structure.
  */
GkrellmMonitor *
gkrellm_init_plugin(void) {
  panel_style_id = gkrellm_add_meter_style(&wireless_meter,"wireless");
  monitor = &wireless_meter;
  return monitor;
}
