Logo Search packages:      
Sourcecode: naim version File versions

conio.c

/*  _ __   __ _ ___ __  __
** | '_ \ / _` |_ _|  \/  | naim
** | | | | (_| || || |\/| | Copyright 1998-2004 Daniel Reed <n@ml.org>
** |_| |_|\__,_|___|_|  |_| ncurses-based chat client
*/
#include <naim/naim.h>
#include <naim/modutil.h>
#include <ltdl.h>

#include <fcntl.h>
#include <signal.h>
#include <unistd.h>

#include "naim-int.h"
#include "conio_keys.h"
#include "snapshot.h"

#ifdef HAVE_HSTRERROR
# include <netdb.h>
#endif

#define CTRL(x)   ((x)-'A'+1)

#ifndef CONIOCPP
# define CONIO_C
# include "conio_cmds.h"
#endif

extern win_t      win_input, win_buddy, win_info;
extern conn_t     *curconn;
extern faimconf_t faimconf;
extern int  stayconnected, quakeoff;
extern time_t     now, awaytime;
extern double     nowf, changetime;
extern const char *home, *sty;

extern int
      scrollbackoff G_GNUC_INTERNAL,
      needpass G_GNUC_INTERNAL,
      doredraw G_GNUC_INTERNAL,
      inpaste G_GNUC_INTERNAL,
      withtextcomp G_GNUC_INTERNAL;
extern char
      *namesbuf G_GNUC_INTERNAL,
      *lastclose G_GNUC_INTERNAL;
extern namescomplete_t
      namescomplete G_GNUC_INTERNAL;
int   scrollbackoff = 0,
      needpass = 0,
      doredraw = 0,
      consolescroll = -1,
      inpaste = 0,
      withtextcomp = 0;
char  *namesbuf = NULL,
      *lastclose = NULL;
namescomplete_t
      namescomplete;

static const char
      *collist[] = {
      "CLEAR/BLACK",
      "RED",
      "GREEN",
      "YELLOW/BROWN",
      "BLUE",
      "MAGENTA/PINK",
      "CYAN",
      "WHITE/GREY"
};

static const char
      *forelist[] = {
      "EVENT",
      "EVENT_ALT",
      "TEXT",
      "SELF",
      "BUDDY",
      "BUDDY_WAITING",
      "BUDDY_ADDRESSED",
      "BUDDY_IDLE",
      "BUDDY_AWAY",
      "BUDDY_OFFLINE",
      "BUDDY_QUEUED",
      "BUDDY_TAGGED",
};

static const char
      *backlist[] = {
      "INPUT",
      "WINLIST",
      "WINLISTHIGHLIGHT",
      "CONN",
      "IMWIN",
      "STATUSBAR",
};

static struct {
      const char
            *key,
            *cmd;
} conio_bind_defaultar[] = {
      { "up",           ":INPUT_SCROLL_BACK" },
      { "down",   ":INPUT_SCROLL_FORWARD" },
      { "left",   ":INPUT_CURSOR_LEFT" },
      { "right",  ":INPUT_CURSOR_RIGHT" },
      { "tab",    ":TAB_BUDDY_NEXT" },
      { "btab",   ":BUDDY_PREV" },
      { "M-tab",  ":BUDDY_PREV" },
      { "space",  ":SPACE_OR_NBSP" },
      { "end",    ":BUDDY_NEXT" },
      { "c1",           ":BUDDY_NEXT" },
      { "home",   ":BUDDY_PREV" },
      { "a1",           ":BUDDY_PREV" },
      { "dc",           ":CONN_NEXT" },
      { "ic",           ":CONN_PREV" },
      { "backspace",    ":INPUT_BACKSPACE" },
      { "^D",           ":INPUT_DELETE" },
      { "enter",  ":INPUT_ENTER" },
      { "ppage",  ":BUDDY_SCROLL_BACK" },
      { "a3",           ":BUDDY_SCROLL_BACK" },
      { "^R",           ":BUDDY_SCROLL_BACK" },
      { "npage",  ":BUDDY_SCROLL_FORWARD" },
      { "c3",           ":BUDDY_SCROLL_FORWARD" },
      { "^Y",           ":BUDDY_SCROLL_FORWARD" },
      { "^A",           ":INPUT_CURSOR_HOME" },
      { "^E",           ":INPUT_CURSOR_END" },
      { "b2",           ":INPUT_CURSOR_HOME_END" },
      { "^P",           ":INPUT_PASTE" },
      { "<",            ":INPUT_SYM_LT" },
      { ">",            ":INPUT_SYM_GT" },
      { "&",            ":INPUT_SYM_AMP" },
      { "F1",           ":STATUS_DISPLAY" },
      { "^K",           ":INPUT_KILL_EOL" },
      { "F2",           ":INPUT_ENT_BOLD" },
      { "^B",           ":INPUT_ENT_BOLD" },
      { "F3",           ":INPUT_ENT_ITALIC" },
      { "^V",           ":INPUT_ENT_ITALIC" },
      { "F4",           ":WINLIST_HIDE" },
      { "M-w",    ":WINLIST_HIDE" },
      { "F5",           ":STATUS_POKE" },
      { "M-s",    ":STATUS_POKE" },
      { "F6",           "/eval /clear; echo Screen cleared" },
      { "^L",           ":REDRAW" },
      { "F7",           "/jumpback" },
      { "M-b",    "/jumpback" },
      { "F8",           "/jump" },
      { "^N",           "/jump" },
      { "M-n",    "/jump" },
      { "F9",           "/away" },
      { "M-A",    "/away" },
      { "F10",    "/close" },
      { "^W",           ":INPUT_KILL_WORD" },
      { "^T",           ":INPUT_KILL" },
      { "^U",           ":INPUT_KILL" },
      { "sighup", "/readprofile .naimprofile" },
      { "sigusr1",      "/echo User signal 1 received (SIGUSR1)" },
      { "sigusr2",      "/echo User signal 2 received (SIGUSR2)" },
};
static const int
      conio_bind_defaultc = sizeof(conio_bind_defaultar)/sizeof(*conio_bind_defaultar);

static void
      conio_bind_defaults(void) {
      int   i;

      for (i = 0; i < conio_bind_defaultc; i++) {
            char  buf[1024];

            snprintf(buf, sizeof(buf), "bind %s %s", conio_bind_defaultar[i].key,
                  conio_bind_defaultar[i].cmd);
            conio_handlecmd(buf);
      }
}

static const char
      *conio_bind_getdef(const char *key) {
      int   i;

      for (i = 0; i < conio_bind_defaultc; i++)
            if (strcasecmp(key, conio_bind_defaultar[i].key) == 0)
                  return(conio_bind_defaultar[i].cmd);
      return(NULL);
}

static struct {
      int   key;
      char  *script;
      void  (*func)(char *, int *);
} *conio_bind_ar = NULL;
static int
      conio_bind_arlen = 0;

void  *conio_bind_func(int key) {
      int   i;

      for (i = 0; i < conio_bind_arlen; i++)
            if (key == conio_bind_ar[i].key)
                  return(conio_bind_ar[i].func);
      return(NULL);
}

const char  *conio_bind_get(int key) {
      int   i;

      for (i = 0; i < conio_bind_arlen; i++)
            if (key == conio_bind_ar[i].key)
                  return(conio_bind_ar[i].script);
      return(NULL);
}

const char  *conio_bind_get_pretty(int key) {
      const char  *binding = conio_bind_get(key);

      if (binding == NULL)
            return(NULL);
      if (binding[0] != ':')
            return(binding);
      return(conio_key_names[atoi(binding+1)].name);
}

const char  *conio_bind_get_informative(int key) {
      static char buf[1024];
      const char  *binding = conio_bind_get(key);
      int   i;

      if (binding == NULL)
            return(NULL);
      if (binding[0] == '/') {
            const char
                  *end = strchr(binding, ' ');
            int   i;

            if (end == NULL)
                  end = binding+strlen(binding);
            for (i = 0; i < cmdc; i++)
                  if (strncasecmp(binding+1, cmdar[i].c, end-(binding+1)) == 0) {
                        if (cmdar[i].desc == NULL)
                              return(binding);
                        else {
                              snprintf(buf, sizeof(buf), "%-25s  %s", binding, cmdar[i].desc);
                              return(buf);
                        }
                  }
            return(binding);
      }
      if (binding[0] != ':')
            return(binding);
      i = atoi(binding+1);
      if (*(conio_key_names[i].desc) == 0)
            return(conio_key_names[i].name);
      else {
            snprintf(buf, sizeof(buf), "%-25s  %s", conio_key_names[i].name, conio_key_names[i].desc);
            return(buf);
      }
}

void  conio_bind_set(int key, const char *script, void (*func)(char *, int *)) {
      int   i;

      for (i = 0; (i < conio_bind_arlen) && (conio_bind_ar[i].key != key); i++);
      if (i == conio_bind_arlen) {
            conio_bind_arlen++;
            conio_bind_ar = realloc(conio_bind_ar,
                  conio_bind_arlen*sizeof(*conio_bind_ar));
            conio_bind_ar[i].key = key;
            conio_bind_ar[i].script = NULL;
            conio_bind_ar[i].func = NULL;
      }
      free(conio_bind_ar[i].script);
      if (script != NULL)
            conio_bind_ar[i].script = strdup(script);
      else
            conio_bind_ar[i].script = NULL;
      if (func != NULL)
            conio_bind_ar[i].func = func;
}





CONIOFUNC(jump) {
CONIODESC(Go to the specified window or the next 'active' one)
CONIOAOPT(window,winname)
      buddywin_t  *bwin = NULL;
      conn_t      *c = conn;

      if (argc == 0) {
            do {
                  if ((bwin = c->curbwin) != NULL)
                    do {
                        if (bwin->waiting == 1)
                              break;
                    } while ((bwin = bwin->next) != c->curbwin);
                  if (bwin != NULL) {
                        if (bwin->waiting == 1)
                              break;
                        else
                              bwin = NULL;
                  }
            } while ((c = c->next) != conn);
      } else
            if ((bwin = bgetanywin(conn, args[0])) == NULL) {
                  char  *colon;

                  if ((colon = strchr(args[0], ':')) != NULL) {
                        *colon++ = 0;
                        do {
                              if (strcasecmp(args[0], c->winname) == 0)
                                    break;
                        } while ((c = c->next) != conn);
                        if (strcasecmp(args[0], c->winname) != 0) {
                              echof(conn, "JUMP", "No connection named %s.\n",
                                    args[0]);
                              return;
                        }
                        if ((bwin = bgetanywin(c, colon)) == NULL) {
                              echof(conn, "JUMP", "No window in %s named %s.\n",
                                    c->winname, colon);
                              return;
                        }
                  } else {
                        echof(conn, "JUMP", "No window in %s named %s.\n",
                              conn->winname, args[0]);
                        return;
                  }
            }
      if ((bwin != NULL) && (bwin != curconn->curbwin)) {
            if (c != curconn)
                  curconn = c;
            assert(curconn->curbwin != NULL);   /* no way for curconn->curbwin to be NULL if we found a valid bwin! */
            c->curbwin = bwin;
            scrollbackoff = 0;
            bupdate();
            nw_touchwin(&(bwin->nwin));
      }
}

CONIOFUNC(msg) {
CONIOALIA(m)
CONIOALIA(im)
CONIODESC(Send a message; as in /msg naimhelp naim is cool!)
CONIOAREQ(window,name)
CONIOAREQ(string,message)
      buddywin_t  *bwin;
      struct tm   *tmptr = NULL;
      const char  *pre = getvar(conn, "im_prefix"),
                  *post = getvar(conn, "im_suffix");

      if (pre == NULL)
            pre = "";
      if (post == NULL)
            post = "";

      if (args[0] == NULL) {
            bwin = conn->curbwin;
            args[0] = bwin->winname;
      } else if ((bwin = bgetwin(conn, args[0], BUDDY)) == NULL)
            bwin = bgetwin(conn, args[0], CHAT);

      tmptr = localtime(&now);
      assert(tmptr != NULL);

      if (bwin != NULL) {
            const char  *format = NULL;

            switch (bwin->et) {
              case CHAT:
                  format = "&lt;<B>%s</B>&gt;";
                  break;
              case BUDDY:
                  if (bwin->e.buddy->crypt != NULL)
                        format = "<B>%s:</B>";
                  else
                        format = "<B>%s</B>:";
                  break;
              case TRANSFER:
                  echof(conn, "MSG", "You can't send a message to a file transfer!\n");
                  return;
            }

            WINTIME(&(bwin->nwin), IMWIN);
            hwprintf(&(bwin->nwin), C(IMWIN,SELF),
                  format, (conn->sn != NULL)?conn->sn:"(me)");
            hwprintf(&(bwin->nwin), C(IMWIN,TEXT),
                  " %s%s%s<br>", pre, args[1], post);
      }
      if ((conn != curconn) || (conn->curbwin == NULL)
            || (firetalk_compare_nicks(conn->conn, conn->curbwin->winname, args[0]) != FE_SUCCESS)) {
            WINTIME(&(conn->nwin), CONN);
            hwprintf(&(conn->nwin), C(CONN,SELF), "-&gt; *<B>%s</B>*", args[0]);
            hwprintf(&(conn->nwin), C(CONN,TEXT), " %s%s%s<br>", pre, args[1], post);
            naim_lastupdate(conn);
      }

      logim(conn, conn->sn, args[0], args[1]);
      naim_send_im(conn, args[0], args[1], 0);
}

CONIOFUNC(addbuddy) {
CONIOALIA(add)
CONIOALIA(friend)
CONIOALIA(groupbuddy)
CONIODESC(Add someone to your buddy list or change their group membership)
CONIOAREQ(buddy,account)
CONIOAOPT(string,group)
CONIOAOPT(string,realname)
      buddylist_t *blist = rgetlist(conn, args[0]);
      const char  *group = "Buddy", *name = NULL;
      int   already = blist?1:0,
            changed = 0, perm = 1;

      if ((blist != NULL) && !USER_PERMANENT(blist))
            perm = 0;

      switch (argc) {
        default:
            name = args[2];
        case 2:
            group = args[1];
        case 1:
            break;
      }

      /* matching groups must be *exact* matches or AOL rejects the add */
      if (conn->buddyar != NULL) {
            buddylist_t *iter;

            for (iter = conn->buddyar; iter != NULL; iter = iter->next)
                  if (firetalk_compare_nicks(conn->conn, iter->_group, group) == FE_SUCCESS) {
                        if (strcmp(group, iter->_group) != 0)
                              group = iter->_group;
                        break;
                  }
      }

      blist = raddbuddy(conn, args[0], group, name);
      assert(blist != NULL);

      if (argc == 1) {
            if (blist->_name != NULL) {
                  echof(conn, NULL, "Removed <font color=\"#00FFFF\">%s</font>'s name (was \"<font color=\"#00FFFF\">%s</font>\").\n",
                        blist->_account, blist->_name);
                  free(blist->_name);
                  blist->_name = NULL;
                  changed = 1;
            }
      } else if (argc == 2) {
            if ((blist->_group == NULL) || (group == NULL) || (strcmp(blist->_group, group) != 0)) {
                  if (group == NULL) {
                        free(blist->_group);
                        blist->_group = NULL;
                  } else
                        STRREPLACE(blist->_group, group);
                  echof(conn, NULL, "Changed <font color=\"#00FFFF\">%s</font>'s group to \"<font color=\"#FF0000\">%s</font>\".\n",
                        USER_ACCOUNT(blist), USER_GROUP(blist));
                  changed = 1;
            }
      } else {
            if ((blist->_name == NULL) || (strcmp(blist->_name, args[2]) != 0)) {
                  echof(conn, NULL, "Changed <font color=\"#00FFFF\">%s</font>'s name from \"<font color=\"#00FFFF\">%s</font>\" to \"<font color=\"#00FFFF\">%s</font>\".\n",
                        USER_ACCOUNT(blist), USER_NAME(blist), args[2]);
                  STRREPLACE(blist->_name, args[2]);
                  changed = 1;
            }
      }

      if (USER_PERMANENT(blist)) {
            if ((changed == 0) || (perm == 0)) {
                  if (already)
                        echof(conn, NULL, "<font color=\"#00FFFF\">%s</font> <font color=\"#800000\">[<B>%s</B>]</font> is now a permanent buddy.\n",
                              user_name(NULL, 0, conn, blist), USER_GROUP(blist));
                  else
                        echof(conn, NULL, "Added <font color=\"#00FFFF\">%s</font> <font color=\"#800000\">[<B>%s</B>]</font> to your permanent buddy list.\n",
                              user_name(NULL, 0, conn, blist), USER_GROUP(blist));
            }
      } else {
            if ((changed == 0) || (perm == 1)) {
                  if (already)
                        echof(conn, NULL, "<font color=\"#00FFFF\">%s</font> <font color=\"#800000\">[<B>%s</B>]</font> is now a non-permanent buddy.\n",
                              user_name(NULL, 0, conn, blist), USER_GROUP(blist));
                  else
                        echof(conn, NULL, "Added <font color=\"#00FFFF\">%s</font> <font color=\"#800000\">[<B>%s</B>]</font> to your non-permanent buddy list.\n",
                              user_name(NULL, 0, conn, blist), USER_GROUP(blist));
            }
      }

      firetalk_im_add_buddy(conn->conn, args[0], USER_GROUP(blist));
}

static void do_delconn(conn_t *conn) {
      bclearall(conn, 1);

      firetalk_disconnect(conn->conn);
      firetalk_destroy_handle(conn->conn);
      conn->conn = NULL;

      if (conn->next == conn) {
            assert(conn == curconn);
            curconn = NULL;
      } else {
            conn_t      *prev;

            for (prev = conn->next; prev->next != conn; prev = prev->next)
                  ;
            prev->next = conn->next;

            if (curconn == conn)
                  curconn = conn->next;
            conn->next = conn;
      }

      FREESTR(conn->sn);
      FREESTR(conn->password);
      FREESTR(conn->winname);
      FREESTR(conn->server);
      FREESTR(conn->profile);

      if (conn->logfile != NULL) {
            fclose(conn->logfile);
            conn->logfile = NULL;
      }

      nw_delwin(&(conn->nwin));

      assert(conn->buddyar == NULL);
//    assert(conn->idiotar == NULL);
      assert(conn->curbwin == NULL);

      free(conn);
}

CONIOFUNC(exit) {
CONIOALIA(quit)
CONIODESC(Disconnect and exit naim)
      conn_t      *c, *cnext;

      if (secs_getvar_int("autosave") != 0)
            conio_save(conn, 0, NULL);

      c = conn;
      do {
            firetalk_disconnect(c->conn);
      } while ((c = c->next) != conn);

      for (c = curconn; curconn != NULL; c = cnext) {
            cnext = c->next;
            do_delconn(c);
            statrefresh();
      }
      stayconnected = 0;
}

CONIOFUNC(save) {
CONIODESC(Write current settings to ~/.naimrc to be loaded at startup)
CONIOAOPT(string,filename)
      conn_t      *c = conn;
      FILE  *file;
      int   i;
      extern rc_var_s_t
            rc_var_s_ar[];
      extern const int
            rc_var_s_c;
      extern rc_var_i_t
            rc_var_i_ar[];
      extern const int
            rc_var_i_c;
      extern rc_var_i_t
            rc_var_b_ar[];
      extern const int
            rc_var_b_c;
      extern char
            naimrcfilename[];
      const char
            *filename;

      if (argc == 0)
            filename = naimrcfilename;
      else
            filename = args[0];

      if ((file = fopen(filename, "w")) == NULL) {
            echof(conn, "SAVE", "Error %i opening file for write: %s",
                  errno, strerror(errno));
            return;
      }

      fchmod(fileno(file), 0600);

      fprintf(file,
"# naim configuration file.\n"
"# This file was automatically generated by using the /save command from within\n"
"# naim. Feel free to modify it with any text editor.\n"
"# \n"
"# Lines beginning with a # are ignored by naim, so I will use those to document\n"
"# each line I write out. In most cases I will attempt to skip over writing out\n"
"# redundant information, such as when your settings matched the defaults, but\n"
"# in situations where I do include such information I will also place a # in\n"
"# front, to show you what the default value is without actually setting it.\n"
"# \n"
"# The structure of this file is fairly simple: Lines that start with a # are\n"
"# ignored, lines that don't are treated as if you had typed them when first\n"
"# starting naim (so anything that can go after a forward slash while running\n"
"# naim is fair game for inclusion here).\n"
"# \n"
"# If you have any questions about this file, or naim in general, feel free to\n"
"# contact Daniel Reed <n@ml.org>, or visit:\n"
"#    http://naim.n.ml.org/\n"
"\n");

      fprintf(file, "# The window list can either be always visible, always hidden, or auto-hidden.\n");
      if (changetime == -1)
            fprintf(file, "WINLIST Hidden\n\n");
      else if (changetime == 0)
            fprintf(file, "WINLIST Visible\n\n");
      else
            fprintf(file, "#WINLIST Auto\n\n");

      for (i = 0; i < rc_var_s_c; i++) {
            const char
                  *glob = secs_getvar(rc_var_s_ar[i].var),
                  *def = rc_var_s_ar[i].val,
                  *use, *cm, *q;

            if (glob != NULL) {
                  use = glob;
                  if (strcmp(glob, def) == 0)
                        cm = "#";
                  else
                        cm = "";
            } else if (def != NULL) {
                  use = def;
                  cm = "#";
            } else {
                  use = "";
                  cm = "";
            }

            q = ((*use == 0) || ((strchr(use, ' ') != NULL))?"\"":"");

            if (rc_var_s_ar[i].desc != NULL)
                  fprintf(file, "# %s (%s)\n", rc_var_s_ar[i].var, rc_var_s_ar[i].desc);
            else
                  fprintf(file, "# %s\n", rc_var_s_ar[i].var);
            fprintf(file, "%sset %s %s%s%s\n\n", cm, rc_var_s_ar[i].var, q, use, q);
      }

      for (i = 0; i < rc_var_i_c; i++) {
            const char
                  *glob = secs_getvar(rc_var_i_ar[i].var),
                  *cm;
            const int
                  globi = atoi(glob),
                  defi = rc_var_i_ar[i].val;
            int   usei;

            if (glob != NULL) {
                  usei = globi;
                  if (globi == defi)
                        cm = "#";
                  else
                        cm = "";
            } else {
                  usei = defi;
                  cm = "#";
            }

            if (rc_var_i_ar[i].desc != NULL)
                  fprintf(file, "# %s (%s)\n", rc_var_i_ar[i].var, rc_var_i_ar[i].desc);
            else
                  fprintf(file, "# %s\n", rc_var_i_ar[i].var);
            fprintf(file, "%sset %s %i\n\n", cm, rc_var_i_ar[i].var, usei);
      }

      for (i = 0; i < rc_var_b_c; i++) {
            const char
                  *glob = secs_getvar(rc_var_b_ar[i].var),
                  *cm;
            const int
                  globi = atoi(glob),
                  defi = rc_var_b_ar[i].val;
            int   usei;

            if (glob != NULL) {
                  usei = globi;
                  if (globi == defi)
                        cm = "#";
                  else
                        cm = "";
            } else {
                  usei = defi;
                  cm = "#";
            }

            if (rc_var_b_ar[i].desc != NULL)
                  fprintf(file, "# %s (%s)\n", rc_var_b_ar[i].var, rc_var_b_ar[i].desc);
            else
                  fprintf(file, "# %s\n", rc_var_b_ar[i].var);
            fprintf(file, "%sset %s %i\n\n", cm, rc_var_b_ar[i].var, usei);
      }

      {
            faimconf_t  fc;

            rc_initdefs(&fc);

            fprintf(file, "# Colors\n");
            for (i = 0; i < sizeof(forelist)/sizeof(*forelist); i++)
                  fprintf(file, "%ssetcol %s %s%s\n", (fc.f[i] == faimconf.f[i])?"#":"",
                        forelist[i], collist[faimconf.f[i]%(nw_COLORS*nw_COLORS)],
                        (faimconf.f[i]>=2*(nw_COLORS*nw_COLORS))?" BOLD":(faimconf.f[i]>=(nw_COLORS*nw_COLORS))?" DULL":"");
            for (i = 0; i < sizeof(backlist)/sizeof(*backlist); i++)
                  fprintf(file, "%ssetcol %s %s\n", (fc.b[i] == faimconf.b[i])?"#":"",
                        backlist[i], collist[faimconf.b[i]]);
            fprintf(file, "\n");
      }

      {
            const char  *binding, *def;
            char  key[10];
            int   base;

            fprintf(file, "# Key bindings.\n");
            for (base = 0; base <= KEY_MAX; base += KEY_MAX) {
                  for (i = 0; i < sizeof(conio_bind_names)/sizeof(*conio_bind_names); i++)
                        if ((binding = conio_bind_get_pretty(base+conio_bind_names[i].code)) != NULL) {
                              snprintf(key, sizeof(key), "%s%s", (base?"M-":""), conio_bind_names[i].name);

                              if (((def = conio_bind_getdef(key)) == NULL)
                                    || (strcmp(def, binding) != 0))
                                    fprintf(file, "bind %s %s\n", key, binding);
                        }
                  for (i = 1; i <= 12; i++)
                        if ((binding = conio_bind_get_pretty(base+KEY_F(i))) != NULL) {
                              snprintf(key, sizeof(key), "%sF%i", (base?"M-":""), i);

                              if (((def = conio_bind_getdef(key)) == NULL)
                                    || (strcmp(def, binding) != 0))
                                    fprintf(file, "bind %s %s\n", key, binding);
                        }
                  for (i = 'A'; i <= 'Z'; i++)
                        if ((binding = conio_bind_get_pretty(base+CTRL(i))) != NULL) {
                              snprintf(key, sizeof(key), "%s^%c", (base?"M-":""), i);

                              if (((def = conio_bind_getdef(key)) == NULL)
                                    || (strcmp(def, binding) != 0))
                                    fprintf(file, "bind %s %s\n", key, binding);
                        }
            }
            for (i = 'a'; i <= 'z'; i++)
                  if ((binding = conio_bind_get_pretty(KEY_MAX+i)) != NULL) {
                        char  key[4] = { 'M', '-', i, 0 };

                        if (((def = conio_bind_getdef(key)) == NULL)
                              || (strcmp(def, binding) != 0))
                              fprintf(file, "bind %s %s\n", key, binding);
                  }
            fprintf(file, "\n");
      }

      if (awaytime > 0)
            fprintf(file, "# You were away when you /saved.\nAWAY\n\n");

      {
            extern script_t
                  *scriptar;
            extern int
                  scriptc;
            int   i;

            if (scriptc > 0) {
                  fprintf(file, "# Aliases.\n");
                  for (i = 0; i < scriptc; i++)
                        fprintf(file, "ALIAS %s %s\n", scriptar[i].name, scriptar[i].script);
                  fprintf(file, "\n");
            }
      }

      {
            extern html_clean_t
                  *html_cleanar;
            extern int
                  html_cleanc;
            int   i;

            fprintf(file, "# Filters.\n");
            fprintf(file, "FILTER REPLACE :FLUSH\n");
            for (i = 0; i < html_cleanc; i++)
                  if (*html_cleanar[i].from != 0)
                        fprintf(file, "FILTER REPLACE %s %s\n",
                              html_cleanar[i].from, html_cleanar[i].replace);
            fprintf(file, "\n");
      }

      fprintf(file, "\n");

      do {
            fprintf(file,
"\n"
"\n"
"# Create a new connection called %s of type %s.\n"
"newconn %s %s\n"
"\n",                   c->winname, firetalk_strprotocol(c->proto),
                        c->winname, firetalk_strprotocol(c->proto));

            fprintf(file,
"# naim will automatically attempt to read your profile from\n"
"# ~/.naimprofile, though if you prefer you can specify one\n"
"# to replace it here.\n"
"#%s:readprofile %s/.naimprofile\n"
"\n",                   c->winname, home);

            {
                  buddylist_t *blist = c->buddyar;

                  if (blist != NULL) {
                    fprintf(file, "# Add your buddies.\n");
                    do {
                      if (USER_PERMANENT(blist)) {
                        char  *b = USER_ACCOUNT(blist),
                              *g = USER_GROUP(blist),
                              *n = (blist->_name != NULL)?blist->_name:"",
                              *bq = (strchr(b, ' ') != NULL)?"\"":"",
                              *gq = ((*g == 0)
                                    || (strchr(g, ' ') != NULL))?"\"":"";

                        fprintf(file,
"%s:addbuddy\t%s%s%s\t%s%s%s\t%s\n",
                              c->winname, bq, b, bq, gq, g, gq, n);
                      }
                    } while ((blist = blist->next) != NULL);
                    fprintf(file, "\n");
                  }
            }

            {
                  ignorelist_t      *blist = c->idiotar;

                  if (blist != NULL) {
                    fprintf(file, "# Add your ignore list.\n");
                    do {
                        char  *b = blist->screenname,
                              *n = (blist->notes != NULL)?blist->notes:"",
                              *bq = (strchr(b, ' ') != NULL)?"\"":"";

                        if (strcasecmp(n, "block") == 0)
                              fprintf(file,
"%s:block\t%s\n",
                              c->winname, b);
                        else
                              fprintf(file,
"%s:ignore\t%s%s%s\t%s\n",
                              c->winname, bq, b, bq, n);
                    } while ((blist = blist->next) != NULL);
                    fprintf(file, "\n");
                  }
            }

            {
                  buddywin_t  *bwin = c->curbwin;

                  if (bwin != NULL) {
                    fprintf(file, "# Rejoin your channels.\n");
                    do {
                        if ((bwin->et == CHAT) && (*(bwin->winname) != ':'))
                              fprintf(file,
"%s:join %s%s%s%s%s\n",
                                    c->winname,
                                    strchr(bwin->winname, ' ')?"\"":"", bwin->winname, strchr(bwin->winname, ' ')?"\"":"",
                                    bwin->e.chat->key?" ":"", bwin->e.chat->key?bwin->e.chat->key:"");
                        else if (bwin->pouncec > 0) {
                              int   i;

                              fprintf(file,
"%s:open %s\n",
                                    c->winname, bwin->winname);
                              for (i = 0; i < bwin->pouncec; i++)
                                    fprintf(file,
"%s:msg \"%s\" %s\n",
                                          c->winname, bwin->winname, bwin->pouncear[i]);
                        }
                    } while ((bwin = bwin->next) != c->curbwin);
                    fprintf(file, "\n");
                  }
            }

            {
                  int   log = getvar_int(c, "log");

                  fprintf(file,
"# Logging\n"
"%sset %s:log\t%i\n"
"%sset %s:logfile\t%s%s%s%s\n"
"\n",                   log?"":"#", c->winname, log,
                        log?"":"#", c->winname,
                              log?getvar(c, "logfile"):home,
                              log?"":"/",
                              log?"":c->winname,
                              log?"":".log");
            }

            for (i = 0; i < rc_var_s_c; i++) {
                  const char
                        *loc = getvar(c, rc_var_s_ar[i].var),
                        *glob = secs_getvar(rc_var_s_ar[i].var),
                        *q;

                  if (loc != glob) {
                        q = ((*loc == 0) || ((strchr(loc, ' ') != NULL))?"\"":"");

                        if (rc_var_s_ar[i].desc != NULL)
                              fprintf(file, "# %s-specific %s (see above)\n", c->winname, 
                                    rc_var_s_ar[i].var);
                        else
                              fprintf(file, "# %s-specific %s\n", c->winname,
                                    rc_var_s_ar[i].var);
                        fprintf(file, "set %s:%s %s%s%s\n\n", c->winname,
                              rc_var_s_ar[i].var, q, loc, q);
                  }
            }

            for (i = 0; i < rc_var_i_c; i++) {
                  const char
                        *loc = getvar(c, rc_var_i_ar[i].var),
                        *glob = secs_getvar(rc_var_i_ar[i].var);

                  if (loc != glob) {
                        if (rc_var_i_ar[i].desc != NULL)
                              fprintf(file, "# %s-specific %s (see above)\n", c->winname,
                                    rc_var_i_ar[i].var);
                        else
                              fprintf(file, "# %s-specific %s\n", c->winname,
                                    rc_var_i_ar[i].var);
                        fprintf(file, "set %s:%s %s\n\n", c->winname,
                              rc_var_i_ar[i].var, loc);
                  }
            }

            for (i = 0; i < rc_var_b_c; i++) {
                  const char
                        *loc = getvar(c, rc_var_b_ar[i].var),
                        *glob = secs_getvar(rc_var_b_ar[i].var);

                  if (loc != glob) {
                        if (rc_var_b_ar[i].desc != NULL)
                              fprintf(file, "# %s-specific %s (see above)\n", c->winname,
                                    rc_var_b_ar[i].var);
                        else
                              fprintf(file, "# %s-specific %s\n", c->winname,
                                    rc_var_b_ar[i].var);
                        fprintf(file, "set %s:%s %s\n\n", c->winname,
                              rc_var_b_ar[i].var, loc);
                  }
            }

            {
                  char  *name = c->sn,
                        *nameq = ((name == NULL) || (strchr(name, ' ') != NULL))?"\"":"",
                        *pass = getvar(c, "password");

                  fprintf(file, "# Connect to %s.\n", c->winname);
                  if (pass != NULL) {
                        char  *passq = (strchr(pass, ' ') != NULL)?"\"":"";

                        fprintf(file, "set %s:password %s%s%s\nclear\n",
                              c->winname, passq, pass, passq);
                  } else
                        fprintf(file,     "#set %s:password mypass\n"
                                    "#clear\n"
                                    "# naim will prompt you for your password if it's not supplied here.\n",
                              c->winname);
                  fprintf(file, "%s%s:connect %s%s%s", ((name != NULL) && (conn->online > 0))?"":"#",
                        c->winname, nameq, name?name:"myname", 
                        nameq);
                  if (c->server != NULL) {
                        fprintf(file, " %s", c->server);
                        if (c->port > 0)
                              fprintf(file, ":%i", c->port);
                  }
            }

            fprintf(file, "\n");
      } while ((c = c->next) != conn);

      fclose(file);
      echof(conn, NULL, "Settings saved to %s.\n", filename);
}

CONIOFUNC(sync) {
CONIODESC(Save buddy list to server)
      firetalk_save_config(conn->conn);
      echof(conn, NULL, "Settings saved to server.\n");
}

CONIOFUNC(connect) {
CONIODESC(Connect to a service)
CONIOAOPT(string,name)
CONIOAOPT(string,server)
CONIOAOPT(int,port)
      fte_t fte;

      if (conn->online > 0) {
            echof(conn, "CONNECT", "Please <font color=\"#00FF00\">/%s:disconnect</font> first (just so there's no confusion).\n",
                  conn->winname);
            return;
      }

      if ((argc == 1) && (strchr(args[0], '.') != NULL) && (strchr(args[0], '@') == NULL)) {
            if (conn->sn != NULL) {
                  echof(conn, "CONNECT", "It looks like you're specifying a server, so I'll use %s as your name.\n",
                        conn->sn);
                  args[1] = args[0];
                  args[0] = conn->sn;
                  argc = 2;
            } else {
                  echof(conn, "CONNECT", "It looks like you're specifying a server, but I don't have a default name to use.\n");
                  echof(conn, "CONNECT", "Please specify a name to use (<font color=\"#00FF00\">/%s:connect myname %s</font>).\n",
                        conn->winname, args[0]);
                  return;
            }
      }

      switch(argc) {
        case 3:
            conn->port = atoi(args[2]);
        case 2: {
            char  *tmp;

            if ((tmp = strchr(args[1], ':')) != NULL) {
                  conn->port = atoi(tmp+1);
                  *tmp = 0;
            }
            STRREPLACE(conn->server, args[1]);
        }
        case 1:
            if ((conn->sn == NULL) || ((conn->sn != args[0]) && (strcmp(conn->sn, args[0]) != 0)))
                  STRREPLACE(conn->sn, args[0]);
      }

      if (conn->sn == NULL) {
            echof(conn, "CONNECT", "Please specify a name to use (<font color=\"#00FF00\">/%s:connect myname</font>).\n",
                  conn->winname);
            return;
      }

      if (conn->port == 0) {
            if (conn->server == NULL)
                  echof(conn, NULL, "Connecting to %s.\n",
                        firetalk_strprotocol(conn->proto));
            else
                  echof(conn, NULL, "Connecting to %s on server %s.\n",
                        firetalk_strprotocol(conn->proto), conn->server);
      } else {
            if (conn->server == NULL)
                  echof(conn, NULL, "Connecting to %s on port %i.\n",
                        firetalk_strprotocol(conn->proto), conn->port);
            else
                  echof(conn, NULL, "Connecting to %s on port %i of server %s.\n",
                        firetalk_strprotocol(conn->proto), conn->port, conn->server);
      }

      statrefresh();

#ifdef HAVE_HSTRERROR
      h_errno = 0;
#endif
      errno = 0;
      switch ((fte = firetalk_signon(conn->conn, conn->server, conn->port, conn->sn))) {
        case FE_SUCCESS:
            break;
        case FE_CONNECT:
#ifdef HAVE_HSTRERROR
            if (h_errno != 0)
                  echof(conn, "CONNECT", "Unable to connect: %s (%s).\n",
                        firetalk_strerror(firetalkerror), hstrerror(h_errno));
            else
#endif
            if (errno != 0)
                  echof(conn, "CONNECT", "Unable to connect: %s (%s).\n",
                        firetalk_strerror(firetalkerror), strerror(errno));
            else
                  echof(conn, "CONNECT", "Unable to connect: %s.\n",
                        firetalk_strerror(firetalkerror));
            break;
        default:
            echof(conn, "CONNECT", "Connection failed in startup, %s.\n",
                  firetalk_strerror(fte));
            break;
      }
}

CONIOFUNC(jumpback) {
CONIODESC(Go to the previous window)
      conn_t      *c = conn,
            *newestconn = NULL;
      buddywin_t *newestbwin = NULL;
      double      newestviewtime = 0;

      do {
            buddywin_t  *bwin;

            if ((bwin = c->curbwin) != NULL)
              do {
                  if (((c != conn) || (bwin != conn->curbwin)) && (bwin->viewtime > newestviewtime)) {
                        newestconn = c;
                        newestbwin = bwin;
                        newestviewtime = bwin->viewtime;
                  }
              } while ((bwin = bwin->next) != c->curbwin);
      } while ((c = c->next) != conn);

      if (newestconn != NULL) {
            assert(newestbwin != NULL);
            if (newestconn != curconn)
                  curconn = newestconn;
            assert(curconn->curbwin != NULL);   /* no way for curconn->curbwin to be NULL if we found a valid bwin! */
            newestconn->curbwin = newestbwin;
            scrollbackoff = 0;
            bupdate();
            nw_touchwin(&(newestbwin->nwin));
      }
}

CONIOFUNC(info) {
CONIOALIA(whois)
CONIOALIA(wi)
CONIODESC(Retrieve a user profile)
CONIOAOPT(entity,name)
      if (argc == 0) {
            if (!inconn || (conn->curbwin->et != BUDDY))
                  firetalk_im_get_info(conn->conn, conn->sn);
            else
                  firetalk_im_get_info(conn->conn, conn->curbwin->winname);
      } else
            firetalk_im_get_info(conn->conn, args[0]);
}

CONIOFUNC(eval) {
CONIODESC(Evaluate a command with $-variable substitution)
CONIOAREQ(string,script)
      secs_script_parse(args[0]);
}

CONIOFUNC(say) {
CONIODESC(Send a message to the current window; as in /say I am happy)
CONIOAREQ(string,message)
CONIOWHER(NOTSTATUS)
      const char
            *newargs[2] = { conn->curbwin->winname, args[0] };

      conio_msg(conn, 2, newargs);
}

CONIOFUNC(me) {
CONIODESC(Send an 'action' message to the current window; as in /me is happy)
CONIOAREQ(string,message)
CONIOWHER(NOTSTATUS)
      WINTIME(&(conn->curbwin->nwin), IMWIN);
      hwprintf(&(conn->curbwin->nwin), C(IMWIN,SELF), "* <B>%s</B>", conn->sn);
      hwprintf(&(conn->curbwin->nwin), C(IMWIN,TEXT), " %s<br>", args[0]);
      logim(conn, conn->sn, conn->curbwin->winname, args[0]);
      naim_send_act(conn, conn->curbwin->winname, args[0]);
}

CONIOFUNC(open) {
CONIOALIA(window)
CONIODESC(Open a query window)
CONIOAREQ(buddy,name)
      buddywin_t  *bwin;

      if ((bwin = bgetanywin(conn, args[0])) != NULL)
            echof(conn, "OPENWIN", "There is already a window open for <font color=\"#00FFFF\">%s</font>. Type <font color=\"#00FF00\">/jump %s</font> to jump to it.\n",
                  bwin->winname, args[0]);
      else {
            buddylist_t *blist;
            int   added;

            blist = rgetlist(conn, args[0]);
            if (blist == NULL) {
                  blist = raddbuddy(conn, args[0], DEFAULT_GROUP, NULL);
                  firetalk_im_add_buddy(conn->conn, args[0], USER_GROUP(blist));
                  added = 1;
            } else
                  added = 0;

            bnewwin(conn, USER_ACCOUNT(blist), BUDDY);
            bwin = bgetwin(conn, USER_ACCOUNT(blist), BUDDY);
            assert(bwin != NULL);
            if (added)
                  window_echof(bwin, "Query window created and user added as a temporary buddy.\n");
            else
                  window_echof(bwin, "Query window created.\n");
            conn->curbwin = bwin;
            nw_touchwin(&(bwin->nwin));
            scrollbackoff = 0;
            naim_changetime();
            bupdate();
      }
}

CONIOFUNC(close) {
CONIOALIA(endwin)
CONIOALIA(part)
CONIODESC(Close a query window or leave a discussion)
CONIOAOPT(window,winname)
CONIOWHER(NOTSTATUS)
      buddywin_t  *bwin;

      if (argc == 1) {
            if ((bwin = bgetanywin(conn, args[0])) == NULL) {
                  echof(conn, "CLOSE", "No window is open for <font color=\"#00FFFF\">%s</font>.\n",
                        args[0]);
                  return;
            }
      } else
            bwin = conn->curbwin;

      bclose(conn, bwin, 0);
      bwin = NULL;
}

CONIOFUNC(closeall) {
CONIODESC(Close stale windows for offline buddies)
      int   i, l;
      buddywin_t  *bwin;

      if (conn->curbwin == NULL)
            return;

      l = 0;
      bwin = conn->curbwin;
      do {
            l++;
      } while ((bwin = bwin->next) != conn->curbwin);
      for (i = 0; i < l; i++) {
            buddywin_t
                  *bnext = bwin->next;
            const char
                  *_args[1];

            _args[0] = bwin->winname;
            if ((bwin->et == BUDDY) && (bwin->e.buddy->offline > 0) && (bwin->pouncec == 0))
                  conio_close(conn, 1, _args);
            else if ((bwin->et == CHAT) && (bwin->e.chat->offline > 0))
                  conio_close(conn, 1, _args);
            bwin = bnext;
      }
}

CONIOFUNC(ctcp) {
CONIODESC(Send Client To Client Protocol request to someone)
CONIOAREQ(window,name)
CONIOAOPT(string,requestname)
CONIOAOPT(string,message)
      if (argc == 1)
            firetalk_subcode_send_request(conn->conn, args[0],
                  "VERSION", NULL);
      else if (argc == 2)
            firetalk_subcode_send_request(conn->conn, args[0],
                  args[1], NULL);
      else
            firetalk_subcode_send_request(conn->conn, args[0],
                  args[1], args[2]);
}

CONIOFUNC(clear) {
CONIODESC(Temporarily blank the scrollback for the current window)
      win_t *win;
      int   i;

      if (inconn) {
            assert(conn->curbwin != NULL);
            win = &(conn->curbwin->nwin);
      } else
            win = &(conn->nwin);

      nw_erase(win);
      for (i = 0; i < faimconf.wstatus.pady; i++)
            nw_printf(win, 0, 0, "\n");
}

CONIOFUNC(clearall) {
CONIODESC(Perform a /clear on all open windows)
      conn_t      *c = conn;

      do {
            buddywin_t  *bwin = c->curbwin;

            if (bwin != NULL)
                  do {
                        int   i;

                        nw_erase(&(bwin->nwin));
                        for (i = 0; i < faimconf.wstatus.pady; i++)
                              nw_printf(&(bwin->nwin), 0, 0, "\n");
                        bwin->nwin.dirty = 0;
                  } while ((bwin = bwin->next) != c->curbwin);
      } while ((c = c->next) != conn);
}

CONIOFUNC(load) {
CONIODESC(Load a command file (such as .naimrc))
CONIOAREQ(filename,filename)
      naim_read_config(args[0]);
}

CONIOFUNC(away) {
CONIODESC(Set or unset away status)
CONIOAOPT(string,message)
      if (argc == 0) {
            if (awaytime > 0)
                  unsetaway();
            else
                  setaway(0);
      } else {
            secs_setvar("awaymsg", secs_script_expand(NULL, args[0]));
            setaway(0);
      }
}

CONIOFUNC(names) {
CONIOALIA(buddylist)
CONIODESC(Display buddy list or members of a chat)
CONIOAOPT(chat,chat)
      const char  *chat;

      if ((argc == 0) || (strcasecmp(args[0], "ON") == 0) || (strcasecmp(args[0], "OFF") == 0)) {
            int   showon = 1, showoff = 1;

            if ((argc > 0) && (strcasecmp(args[0], "ON") == 0))
                  showoff = 0;
            else if ((argc > 0) && (strcasecmp(args[0], "OFF") == 0))
                  showon = 0;

            if (!inconn || !showon || !showoff || (conn->curbwin->et != CHAT) || (*(conn->curbwin->winname) == ':')) {
                  buddylist_t *blist;
                  int   maxname = strlen("Account"), maxgroup = strlen("Group"), maxnotes = strlen("Name"), max;
                  char  *spaces;

                  if (conn->buddyar == NULL) {
                        echof(conn, NULL, "Your buddy list is empty, try <font color=\"#00FF00\">/addbuddy buddyname</font>.\n");
                        return;
                  }

                  echof(conn, NULL, "Buddy list:");

                  blist = conn->buddyar;
                  do {
                        const char  *name = USER_ACCOUNT(blist),
                                    *nameq = ((*name == 0) || strchr(name, ' '))?"\"":"",
                                    *group = USER_GROUP(blist),
                                    *groupq = ((*group == 0) || strchr(group, ' '))?"\"":"",
                                    *notes = USER_NAME(blist),
                                    *notesq = ((*notes == 0) || strchr(notes, ' '))?"\"":"";
                        int         namelen = strlen(name)+2*strlen(nameq),
                                    grouplen = strlen(group)+2*strlen(groupq),
                                    noteslen = strlen(notes)+2*strlen(notesq);

                        if (namelen > maxname)
                              maxname = namelen;
                        if (grouplen > maxgroup)
                              maxgroup = grouplen;
                        if (noteslen > maxnotes)
                              maxnotes = noteslen;
                  } while ((blist = blist->next) != NULL);

                  if (maxname > maxgroup)
                        max = maxname;
                  else
                        max = maxgroup;
                  if (maxnotes > max)
                        max = maxnotes;

                  if ((spaces = malloc(max*6 + 1)) == NULL)
                        return;
                  *spaces = 0;
                  while (max > 0) {
                        strcat(spaces, "&nbsp;");
                        max--;
                  }

                  echof(conn, NULL, "</B>&nbsp; %s"
                        " <font color=\"#800000\">%s<B>%s</B>%s</font>%.*s"
                        " <font color=\"#008000\">%s<B>%s</B>%s</font>%.*s"
                        " <font color=\"#000080\">%s<B>%s</B>%s</font>%.*s<B>%s\n",
                        "&nbsp; &nbsp;",
                        "", "Group", "",
                        6*(maxgroup - strlen("Group")), spaces,
                        "", "Account", "",
                        6*(maxname - strlen("Account")), spaces,
                        "", "Name", "",
                        6*(maxnotes - strlen("Name")), spaces,
                        " flags");

                  blist = conn->buddyar;
                  do {
                        const char  *name = USER_ACCOUNT(blist),
                                    *nameq = ((*name == 0) || strchr(name, ' '))?"\"":"",
                                    *group = USER_GROUP(blist),
                                    *groupq = ((*group == 0) || strchr(group, ' '))?"\"":"",
                                    *notes = USER_NAME(blist),
                                    *notesq = ((*notes == 0) || strchr(notes, ' '))?"\"":"";
                        int         namelen = strlen(name)+2*strlen(nameq),
                                    grouplen = strlen(group)+2*strlen(groupq),
                                    noteslen = strlen(notes)+2*strlen(notesq);

                        if ((blist->offline == 0) && !showon)
                              continue;
                        else if ((blist->offline != 0) && !showoff)
                              continue;
                        echof(conn, NULL, "</B>&nbsp; %s"
                              " <font color=\"#800000\">%s<B>%s</B>%s</font>%.*s"
                              " <font color=\"#008000\">%s<B>%s</B>%s</font>%.*s"
                              " <font color=\"#000080\">%s<B>%s</B>%s</font>%.*s<B>%s%s%s%s%s%s%s\n",
                              blist->offline?"OFF":"<B>ON</B>&nbsp;",
                              groupq, group, groupq,
                              6*(maxgroup - grouplen), spaces,
                              nameq, name, nameq,
                              6*(maxname - namelen), spaces,
                              notesq, notes, notesq,
                              6*(maxnotes - noteslen), spaces,

                              USER_PERMANENT(blist)?"":" NON-PERMANENT",
                              blist->crypt?" CRYPT":"",
                              blist->tzname?" TZNAME":"",
                              blist->tag?" TAGGED":"",
                              blist->isaway?" AWAY":"",
                              blist->isidle?" IDLE":"",
                              (blist->peer > 0)?" PEER":"");
                  } while ((blist = blist->next) != NULL);
                  free(spaces);
                  spaces = NULL;
                  echof(conn, NULL, "Use the <font color=\"#00FF00\">/namebuddy</font> command to change a buddy's name, or <font color=\"#00FF00\">/groupbuddy</font> to change a buddy's group.");
                  return;
            }
            chat = conn->curbwin->winname;
      } else
            chat = args[0];

      firetalk_chat_listmembers(conn->conn, chat);
      {
            buddywin_t  *bwin = cgetwin(conn, chat);

            assert(bwin != NULL);
            if (namesbuf == NULL)
                  window_echof(bwin, "Nobody on %s\n", chat);
            else {
                  window_echof(bwin, "Users on %s: %s\n", chat, namesbuf);
                  free(namesbuf);
                  namesbuf = NULL;
            }
      }
}

CONIOFUNC(join) {
CONIODESC(Participate in a chat)
CONIOAREQ(string,chat)
CONIOAOPT(string,key)
      buddywin_t  *cwin;

      if (((cwin = bgetwin(conn, firetalk_chat_normalize(conn->conn, args[0]), CHAT)) == NULL) || (cwin->e.chat->offline != 0)) {
            char  buf[1024];

            cwin = cgetwin(conn, args[0]);
            if (argc > 1) {
                  window_echof(cwin, "Entering chat \"%s\" with a key of \"%s\"; if you intended to join chat \"%s %s\", please use <font color=\"#00FF00\">/join \"%s %s\"</font> in the future.\n",
                        args[0], args[1], args[0], args[1], args[0], args[1]);
                  STRREPLACE(cwin->e.chat->key, args[1]);
            }
            if (cwin->e.chat->key != NULL) {
                  snprintf(buf, sizeof(buf), "%s %s", args[0], cwin->e.chat->key);
                  args[0] = buf;
            }

            if (conn->online > 0)
                  firetalk_chat_join(conn->conn, args[0]);
      }
}

CONIOFUNC(namebuddy) {
CONIODESC(Change the real name for a buddy)
CONIOAREQ(buddy,name)
CONIOAOPT(string,realname)
      if (argc == 1)
            conio_addbuddy(conn, 1, args);
      else {
            const char  *_args[3];

            _args[0] = args[0];
            _args[1] = "Buddy";
            _args[2] = args[1];
            conio_addbuddy(conn, 3, _args);
      }
}

CONIOFUNC(tagbuddy) {
CONIOALIA(tag)
CONIODESC(Mark a buddy with a reminder message)
CONIOAREQ(buddy,name)
CONIOAOPT(string,note)
      buddylist_t *blist = rgetlist(conn, args[0]);

      if (blist == NULL) {
            echof(conn, "TAGBUDDY", "<font color=\"#00FFFF\">%s</font> is not in your buddy list.\n",
                  args[0]);
            return;
      }
      if (argc > 1) {
            STRREPLACE(blist->tag, args[1]);
            echof(conn, NULL, "<font color=\"#00FFFF\">%s</font> is now tagged (%s).\n",
                  user_name(NULL, 0, conn, blist), blist->tag);
      } else if (blist->tag != NULL) {
            free(blist->tag);
            blist->tag = NULL;
            echof(conn, NULL, "<font color=\"#00FFFF\">%s</font> is no longer tagged.\n",
                  user_name(NULL, 0, conn, blist));
      } else
            echof(conn, "TAGBUDDY", "<font color=\"#00FFFF\">%s</font> is not tagged.\n",
                  user_name(NULL, 0, conn, blist));
}

CONIOFUNC(delbuddy) {
CONIODESC(Remove someone from your buddy list)
CONIOAOPT(buddy,name)
      buddylist_t *blist;
      const char  *name;

      if (argc == 0)
            name = lastclose;
      else
            name = args[0];

      if (name == NULL) {
            echof(conn, "DELBUDDY", "No buddy specified.\n");
            return;
      }

      if (bgetwin(conn, name, BUDDY) != NULL) {
            echof(conn, "DELBUDDY", "You can not delete people from your tracking list with a window open. Please <font color=\"#00FF00\">/close %s</font> first.\n",
                  name);
            return;
      }

      if ((blist = rgetlist(conn, name)) != NULL) {
            if (firetalk_im_remove_buddy(conn->conn, name) == FE_SUCCESS)
                  status_echof(conn, "Removed <font color=\"#00FFFF\">%s</font> from your buddy list.\n",
                        user_name(NULL, 0, conn, blist));
            else
                  status_echof(conn, "Removed <font color=\"#00FFFF\">%s</font> from naim's buddy list, but the server wouldn't remove %s%s%s from your session buddy list.\n", 
                        user_name(NULL, 0, conn, blist), strchr(name, ' ')?"\"":"", name, strchr(name, ' ')?"\"":"");
            rdelbuddy(conn, name);
            blist = NULL;
      } else if (firetalk_im_remove_buddy(conn->conn, name) == FE_SUCCESS)
            status_echof(conn, "Removed <font color=\"#00FFFF\">%s</font> from your session buddy list, but <font color=\"#00FFFF\">%s</font> isn't in naim's buddy list.\n",
                  name, name);
      else
            status_echof(conn, "<font color=\"#00FFFF\">%s</font> is not in your buddy list.\n",
                  name);

      if ((argc == 0) && (lastclose != NULL)) {
            free(lastclose);
            lastclose = NULL;
      }
}

CONIOFUNC(op) {
CONIODESC(Give operator privilege)
CONIOAREQ(buddy,name)
CONIOWHER(INCHAT)
      firetalk_chat_op(conn->conn, conn->curbwin->winname, args[0]);
}

CONIOFUNC(deop) {
CONIODESC(Remove operator privilege)
CONIOAREQ(buddy,name)
CONIOWHER(INCHAT)
      firetalk_chat_deop(conn->conn, conn->curbwin->winname, args[0]);
}

CONIOFUNC(topic) {
CONIODESC(View or change current chat topic)
CONIOAOPT(string,topic)
CONIOWHER(INCHAT)
      if (argc == 0) {
            if (conn->curbwin->blurb == NULL)
                  echof(conn, NULL, "No topic set.\n");
            else
                  echof(conn, NULL, "Topic for %s: </B><body>%s</body><B>.\n",
                        conn->curbwin->winname,
                        conn->curbwin->blurb);
      } else
            firetalk_chat_set_topic(conn->conn,
                  conn->curbwin->winname, args[0]);
}

CONIOFUNC(kick) {
CONIODESC(Temporarily remove someone from a chat)
CONIOAREQ(buddy,name)
CONIOAOPT(string,reason)
CONIOWHER(INCHAT)
      firetalk_chat_kick(conn->conn, conn->curbwin->winname, args[0], (argc == 2)?args[1]:conn->sn);
}

CONIOFUNC(invite) {
CONIODESC(Invite someone to a chat)
CONIOAREQ(buddy,name)
CONIOAOPT(string,chat)
CONIOWHER(INCHAT)
      firetalk_chat_invite(conn->conn, conn->curbwin->winname, args[0],
            (argc == 2)?args[1]:"Join me in this Buddy Chat.");
}

CONIOFUNC(help) {
CONIOALIA(about)
CONIODESC(Display topical help on using naim)
CONIOAOPT(string,topic)
      if (argc == 0)
            help_printhelp(NULL);
      else
            help_printhelp(args[0]);
}

CONIOFUNC(unblock) {
CONIOALIA(unignore)
CONIODESC(Remove someone from the ignore list)
CONIOAREQ(buddy,name)
      echof(conn, NULL, "No longer blocking <font color=\"#00FFFF\">%s</font>.\n", args[0]);
      if (conn->online > 0)
            if (firetalk_im_remove_deny(conn->conn, args[0]) != FE_SUCCESS)
                  status_echof(conn, "Removed <font color=\"#00FFFF\">%s</font> from naim's block list, but the server wouldn't remove %s from your session block list.\n",
                        args[0], args[0]);
      rdelidiot(conn, args[0]);
}

CONIOFUNC(block) {
CONIODESC(Server-enforced /ignore)
CONIOAREQ(buddy,name)
CONIOAOPT(string,reason)
      echof(conn, NULL, "Now blocking <font color=\"#00FFFF\">%s</font>.\n", args[0]);
      if (conn->online > 0)
            firetalk_im_add_deny(conn->conn, args[0]);
      raddidiot(conn, args[0], "block");
}

CONIOFUNC(ignore) {
CONIODESC(Ignore all private/public messages)
CONIOAOPT(buddy,name)
CONIOAOPT(string,reason)
      if (argc == 0) {
            ignorelist_t
                  *idiotar = conn->idiotar;

            if (idiotar == NULL)
                  echof(conn, NULL, "Ignore list is empty.\n");
            else {
                  echof(conn, NULL, "Ignore list:\n");
                  do {
                        if (idiotar->notes != NULL)
                              echof(conn, NULL, "&nbsp; %s (%s)\n", idiotar->screenname, idiotar->notes);
                        else
                              echof(conn, NULL, "&nbsp; %s\n", idiotar->screenname);
                  } while ((idiotar = idiotar->next) != NULL);
            }
      } else if (argc == 1) {
            if (args[0][0] == '-') {
                  const char
                        *newargs[] = { args[0]+1 };

                  conio_unblock(conn, 1, newargs);
            } else {
                  echof(conn, NULL, "Now ignoring <font color=\"#00FFFF\">%s</font>.\n", args[0]);
                  raddidiot(conn, args[0], NULL);
            }
      } else if (strcasecmp(args[1], "block") == 0)
            conio_block(conn, 1, args);
      else {
            echof(conn, NULL, "Now ignoring <font color=\"#00FFFF\">%s</font> (%s).\n", args[0], args[1]);
            raddidiot(conn, args[0], args[1]);
      }
}

//extern void     (*conio_chains)();
CONIOFUNC(chains) {
CONIOALIA(tables)
CONIODESC(Manipulate data control tables)
CONIOAOPT(string,chain)
      char  buf[1024];
      chain_t     *chain;
      lt_dlhandle self;
      int   i;

      if (argc == 0) {
            const char *chains[] = { "getcmd", "notify", "periodic", "recvfrom", "sendto" };

            for (i = 0; i < sizeof(chains)/sizeof(*chains); i++) {
                  if (i > 0)
                        echof(conn, NULL, "-\n");
                  conio_chains(conn, 1, chains+i);
            }
            echof(conn, NULL, "See <font color=\"#00FF00\">/help chains</font> for more information.\n");
            return;
      }
#ifdef DLOPEN_SELF_LIBNAIM_CORE
      self = lt_dlopen("cygnaim_core-0.dll");
#else
      self = lt_dlopen(NULL);
#endif
      if (self == NULL) {
            echof(conn, "TABLES", "Unable to perform self-symbol lookup: %s.\n",
                  lt_dlerror());
            return;
      }
      snprintf(buf, sizeof(buf), "chain_%s", args[0]);
      if ((chain = lt_dlsym(self, buf)) == NULL) {
            echof(conn, "TABLES", "Unable to find chain %s (%s): %s.\n", 
                  args[0], buf, lt_dlerror());
            lt_dlclose(self);
            return;
      }
      lt_dlclose(self);

      echof(conn, NULL, "Chain %s, containing %i hook%s.\n",
            args[0], chain->count, (chain->count==1)?"":"s");
      for (i = 0; i < chain->count; i++) {
            const char
                  *modname, *hookname;

            if (chain->hooks[i].mod == NULL)
                  modname = "core";
            else {
                  const lt_dlinfo
                        *dlinfo = lt_dlgetinfo(chain->hooks[i].mod);

                  modname = dlinfo->name;
            }
            hookname = chain->hooks[i].name;
            if (*hookname == '_')
                  hookname++;
            if ((strncmp(hookname, modname, strlen(modname)) == 0) && (hookname[strlen(modname)] == '_'))
                  hookname += strlen(modname)+1;
            echof(conn, NULL, " <font color=\"#808080\">%i: <font color=\"#FF0000\">%s</font>:<font color=\"#00FFFF\">%s</font>() weight <B>%i</B> at <B>%#p</B> (%lu passes, %lu stops)</font>\n",
                  i, modname, hookname, chain->hooks[i].weight, chain->hooks[i].func, chain->hooks[i].passes, chain->hooks[i].hits);
      }
}

CONIOFUNC(filter) {
CONIODESC(Manipulate content filters)
CONIOAOPT(string,table)
CONIOAOPT(string,target)
CONIOAOPT(string,action)
      if (argc == 0) {
            echof(conn, NULL, "Current filter tables: REPLACE.\n");
            return;
      }

      if (strcasecmp(args[0], "REPLACE") == 0) {
            extern html_clean_t
                  *html_cleanar;
            extern int
                  html_cleanc;
            int   i;

            if (argc == 1) {
//                echof("REPLACE: Table commands: :FLUSH :LIST :APPEND :DELETE");
                  if (html_cleanc > 0) {
                        for (i = 0; i < html_cleanc; i++)
                              if (*html_cleanar[i].from != 0)
                                    echof(conn, "FILTER REPLACE", "= %s -> %s\n",
                                          html_cleanar[i].from, html_cleanar[i].replace);
                  } else
                        echof(conn, "FILTER REPLACE", "Table empty.\n");
            } else if (argc == 2) {
                  const char  *arg;
                  char  action;

                  switch (args[1][0]) {
                    case '-':
                    case '+':
                    case '=':
                    case ':':
                        action = args[1][0];
                        arg = args[1]+1;
                        break;
                    default:
                        action = '=';
                        arg = args[1];
                        break;
                  }

                  if (action == ':') {
                        if (strcasecmp(arg, "FLUSH") == 0) {
                              for (i = 0; i < html_cleanc; i++) {
                                    free(html_cleanar[i].from);
                                    html_cleanar[i].from = NULL;
                                    free(html_cleanar[i].replace);
                                    html_cleanar[i].replace = NULL;
                              }
                              free(html_cleanar);
                              html_cleanar = NULL;
                              html_cleanc = 0;
                        } else
                              echof(conn, "FILTER REPLACE", "Unknown action ``%s''.\n",
                                    arg);
                  } else if (action == '+')
                        echof(conn, "FILTER REPLACE", "Must specify rewrite rule.\n");
                  else if ((action == '=') || (action == '-')) {
                        for (i = 0; i < html_cleanc; i++)
                              if (strcasecmp(html_cleanar[i].from, arg) == 0) {
                                    if (args[1][0] == '-') {
                                          echof(conn, "FILTER REPLACE", "- %s -> %s\n",
                                                html_cleanar[i].from, html_cleanar[i].replace);
                                          STRREPLACE(html_cleanar[i].from, "");
                                          STRREPLACE(html_cleanar[i].replace, "");
                                    } else
                                          echof(conn, "FILTER REPLACE", "= %s -> %s\n",
                                                html_cleanar[i].from, html_cleanar[i].replace);
                                    return;
                              }
                        echof(conn, "FILTER REPLACE", "%s is not in the table.\n",
                              arg);
                  } else
                        echof(conn, "FILTER REPLACE", "Unknown modifier %c (%s).\n",
                              action, arg);
            } else {
                  for (i = 0; i < html_cleanc; i++)
                        if (strcasecmp(html_cleanar[i].from, args[1]) == 0) {
                              echof(conn, "FILTER REPLACE", "- %s -> %s\n",
                                    html_cleanar[i].from, html_cleanar[i].replace);
                              break;
                        }
                  if (i == html_cleanc)
                        for (i = 0; i < html_cleanc; i++)
                              if (*html_cleanar[i].from == 0)
                                    break;
                  if (i == html_cleanc) {
                        html_cleanc++;
                        html_cleanar = realloc(html_cleanar, html_cleanc*sizeof(*html_cleanar));
                        html_cleanar[i].from = html_cleanar[i].replace = NULL;
                  }
                  STRREPLACE(html_cleanar[i].from, args[1]);
                  STRREPLACE(html_cleanar[i].replace, args[2]);
                  echof(conn, "FILTER REPLACE", "+ %s -> %s\n",
                        html_cleanar[i].from, html_cleanar[i].replace);
            }
      } else
            echof(conn, "FILTER", "Table %s does not exist.",
                  args[0]);
}

static html_clean_t
      conio_filter_defaultar[] = {
      { "u",            "you"       },
      { "ur",           "your"            },
      { "lol",    "<grin>"    },
      { "lawlz",  "<grin>"    },
      { "lolz",   "<grin>"    },
      { "r",            "are"       },
      { "ru",           "are you"   },
      { "some1",  "someone"   },
      { "sum1",   "someone"   },
      { "ne",           "any"       },
      { "ne1",    "anyone"    },
      { "im",           "I'm"       },
      { "b4",           "before"    },
};

static void
      conio_filter_defaults(void) {
      int   i;

      for (i = 0; i < sizeof(conio_filter_defaultar)/sizeof(*conio_filter_defaultar); i++) {
            char  buf[1024];

            snprintf(buf, sizeof(buf), "filter replace %s %s", conio_filter_defaultar[i].from,
                  conio_filter_defaultar[i].replace);
            conio_handlecmd(buf);
      }
}

CONIOFUNC(warn) {
CONIODESC(Send a warning about someone)
CONIOAREQ(buddy,name)
      echof(conn, NULL, "Eek, stay away, <font color=\"#00FFFF\">%s</font> is EVIL!\n", args[0]);
      firetalk_im_evil(conn->conn, args[0]);
}

CONIOFUNC(nick) {
CONIODESC(Change or reformat your name)
CONIOAREQ(string,name)
      if (conn->online > 0)
            firetalk_set_nickname(conn->conn, args[0]);
      else
            echof(conn, "NICK", "Try <font color=\"#00FF00\">/connect %s</font>.\n", args[0]);
}

CONIOFUNC(echo) {
CONIODESC(Display something on the screen with $-variable expansion)
CONIOAREQ(string,script)
      echof(conn, NULL, "%s\n", secs_script_expand(NULL, args[0]));
}

CONIOFUNC(readprofile) {
CONIODESC(Read your profile from disk)
CONIOAREQ(filename,filename)
      const char
            *filename = args[0];
      struct stat
            statbuf;
      size_t      len;
      int   pfd;

      if ((len = strlen(filename)) == 0) {
            echof(conn, "READPROFILE", "Please specify a real file.\n");
            return;
      }
      if (stat(filename, &statbuf) == -1) {
            echof(conn, "READPROFILE", "Can't read %s: %s.\n",
                  filename, strerror(errno));
            return;
      }
      if ((len = statbuf.st_size) < 1) {
            echof(conn, "READPROFILE", "Profile file too small.\n");
            return;
      }
      if ((pfd = open(filename, O_RDONLY)) < 0) {
            echof(conn, "READPROFILE", "Unable to open %s.\n",
                  filename);
            return;
      }
      free(conn->profile);
      if ((conn->profile = malloc(len+1)) == NULL) {
            echof(conn, "READPROFILE", "Fatal error in malloc(%i): %s\n",
                  len+1, strerror(errno));
            return;
      }
      if (read(pfd, conn->profile, len) < len) {
            echof(conn, "READPROFILE", "Fatal error in read(%i): %s\n",
                  pfd, strerror(errno));
            free(conn->profile);
            conn->profile = NULL;
            close(pfd);
            return;
      }
      close(pfd);
      conn->profile[len] = 0;
      if (conn->online > 0)
            naim_set_info(conn->conn, conn->profile);
}

CONIOFUNC(accept) {
CONIODESC(EXPERIMENTAL Accept a file transfer request in the current window)
CONIOAREQ(window,filename)
CONIOWHER(NOTSTATUS)
      if (conn->curbwin->et != TRANSFER) {
            echof(conn, "ACCEPT", "You must be in a file transfer window.\n");
            return;
      }
      firetalk_file_accept(conn->conn, conn->curbwin->e.transfer->handle,
            conn->curbwin, args[0]);
      STRREPLACE(conn->curbwin->e.transfer->local, args[0]);
}

CONIOFUNC(offer) {
CONIODESC(EXPERIMENTAL Offer a file transfer request to someone)
CONIOAREQ(buddy,name)
CONIOAREQ(filename,filename)
      const char
            *from = args[0],
            *filename = args[1];

      if (bgetwin(conn, filename, TRANSFER) == NULL) {
            buddywin_t  *bwin;

            bnewwin(conn, filename, TRANSFER);
            bwin = bgetwin(conn, filename, TRANSFER);
            assert(bwin != NULL);
            bwin->e.transfer = fnewtransfer(NULL, filename, from, -1);
            echof(conn, "OFFER", "Offering file transfer request to <font color=\"#00FFFF\">%s</font> (%s).\n",
                  from, filename);
            firetalk_file_offer(conn->conn, from, filename, bwin);
      } else
            echof(conn, "OFFER", "Ignoring duplicate file transfer request to <font color=\"#00FFFF\">%s</font> (%s).\n",
                  from, filename);
}

CONIOFUNC(setcol) {
CONIODESC(View or change display colors)
CONIOAOPT(string,eventname)
CONIOAOPT(string,colorname)
CONIOAOPT(string,colormodifier)
      int   i, col;

      if (argc == 0) {
            echof(conn, NULL, "Available colors include:\n");
            for (i = 0; i < sizeof(collist)/sizeof(*collist); i++)
                  echof(conn, NULL, "&nbsp;%-2i&nbsp;%s\n", i, collist[i]);
            echof(conn, NULL, "Foregrounds:\n");
            for (i = 0; i < sizeof(forelist)/sizeof(*forelist); i++)
                  echof(conn, NULL, "&nbsp;%-2i&nbsp;%s text is %s.\n", i, forelist[i],
                        collist[faimconf.f[i]%(nw_COLORS*nw_COLORS)]);
            echof(conn, NULL, "Backgrounds:\n");
            for (i = 0; i < sizeof(backlist)/sizeof(*backlist); i++)
                  echof(conn, NULL, "&nbsp;%-2i&nbsp;%s window is %s.\n", i, backlist[i],
                        collist[faimconf.b[i]]);
            return;
      } else if (argc == 1) {
            echof(conn, "SETCOL", "Please see <font color=\"#00FF00\">/help setcol</font>.\n");
            return;
      }

      if (args[1][0] == '0')
            col = 0;
      else if ((col = atoi(args[1])) == 0) {
            for (i = 0; i < sizeof(collist)/sizeof(*collist); i++)
                  if (strncasecmp(collist[i], args[1], strlen(args[1])) == 0)
                        break;
            if (i < sizeof(collist)/sizeof(*collist))
                  col = i;
      }

      for (i = 0; i < sizeof(forelist)/sizeof(*forelist); i++)
            if (strcasecmp(forelist[i], args[0]) == 0)
                  break;
      if (i < sizeof(forelist)/sizeof(*forelist)) {
            if (argc >= 3) {
                  if (strcasecmp(args[2], "BOLD") == 0)
                        col += 2*(nw_COLORS*nw_COLORS);
                  else if (strcasecmp(args[2], "DULL") == 0)
                        col += (nw_COLORS*nw_COLORS);
                  else {
                        echof(conn, "SETCOL", "Unknown modifier %s.\n",
                              args[2]);
                        return;
                  }
            }

            if (faimconf.f[i] != col) {
                  faimconf.f[i] = col;
                  echof(conn, NULL, "Foreground %s set to %s.\n", forelist[i],
                        collist[col%(nw_COLORS*nw_COLORS)]);
            }
      } else {
            for (i = 0; i < sizeof(backlist)/sizeof(*backlist); i++)
                  if (strcasecmp(backlist[i], args[0]) == 0)
                        break;
            if (i < sizeof(backlist)/sizeof(*backlist)) {
                  if (argc >= 3) {
                        echof(conn, "SETCOL", "You can not force bold/dull for backgrounds.\n");
                        return;
                  }

                  if (faimconf.b[i] != col) {
                        conn_t      *c = conn;

                        faimconf.b[i] = col;
                        if ((i == cINPUT) || (i == cSTATUSBAR))
                              nw_flood(&win_info, CB(STATUSBAR,INPUT));

                        if (i == cINPUT)
                              nw_flood(&win_input, (COLORS*col + faimconf.f[cTEXT]%(nw_COLORS*nw_COLORS)));
                        else if (i == cWINLIST)
                              nw_flood(&win_buddy, (COLORS*col + faimconf.f[cBUDDY]%(nw_COLORS*nw_COLORS)));
                        else if ((i != cWINLISTHIGHLIGHT) && (i != cSTATUSBAR))
                          do {
                              if (i == cCONN)
                                    nw_flood(&(conn->nwin), (COLORS*col + faimconf.f[cTEXT]%(nw_COLORS*nw_COLORS)));
                              else if (c->curbwin != NULL) {
                                    buddywin_t  *bwin = c->curbwin;

                                    do {
                                          nw_flood(&(bwin->nwin), (COLORS*col + faimconf.f[cTEXT]%(nw_COLORS*nw_COLORS)));
                                          bwin->nwin.dirty = 1;
                                    } while ((bwin = bwin->next) != c->curbwin);
                              }
                          } while ((c = c->next) != conn);
                        echof(conn, NULL, "Background %s set to %s.\n", backlist[i],
                              collist[col]);
                  }
            } else
                  echof(conn, "SETCOL", "Unknown window/event: %s.\n",
                        args[0]);
      }
}

CONIOFUNC(bind) {
CONIODESC(View or change key bindings)
CONIOAOPT(string,keyname)
CONIOAOPT(string,script)
      int   i, key = 0;

      if (argc == 0) {
            const char  *binding;
            int   base;

            set_echof("    %s%-9s%s  %-25s  %s\n", "", "Key name", "  ", "Script", "Description");
            for (base = 0; base <= KEY_MAX; base += KEY_MAX) {
                  for (i = 0; i < sizeof(conio_bind_names)/sizeof(*conio_bind_names); i++)
                        if ((binding = conio_bind_get_informative(base+conio_bind_names[i].code))
                              != NULL)
                              set_echof("    %s%-9s%s  %s\n", (base?"M-":""), conio_bind_names[i].name,
                                    (base?"":"  "), binding);
                  for (i = 1; i <= 9; i++)
                        if ((binding = conio_bind_get_informative(base+KEY_F(i))) != NULL)
                              set_echof("    %sF%i%s         %s\n", (base?"M-":""), i, (base?"":"  "), binding);
                  for (i = 10; i <= 12; i++)
                        if ((binding = conio_bind_get_informative(base+KEY_F(i))) != NULL)
                              set_echof("    %sF%i%s        %s\n", (base?"M-":""), i, (base?"":"  "), binding);
                  for (i = 'A'; i <= 'Z'; i++)
                        if ((binding = conio_bind_get_informative(base+CTRL(i))) != NULL)
                              set_echof("    %s^%c%s         %s\n", (base?"M-":""), i, (base?"":"  "),
                                    binding);
            }
            for (i = 'a'; i <= 'z'; i++)
                  if ((binding = conio_bind_get_informative(KEY_MAX+i)) != NULL)
                        set_echof("    M-%c          %s\n", i, binding);
            return;
      }

      if ((key = atoi(args[0])) == 0) {
            int   j;

            if ((args[0][0] == 'M') && (args[0][1] == '-')) {
                  key = KEY_MAX;
                  j = 2;
            } else
                  j = 0;
            for (i = 0; i < sizeof(conio_bind_names)/sizeof(*conio_bind_names); i++)
                  if (strcasecmp(conio_bind_names[i].name, args[0]+j) == 0) {
                        key += conio_bind_names[i].code;
                        break;
                  }
            if (i == sizeof(conio_bind_names)/sizeof(*conio_bind_names))
                  key = 0;
      }

      if (key > 0);
      else {
            if ((args[0][0] == 'M') && (args[0][1] == '-')) {
                  key = KEY_MAX;
                  i = 2;
            } else
                  i = 0;

            if (args[0][i+1] == 0) {
                  if (key == 0)
                        key += args[0][i];
                  else
                        key += tolower(args[0][i]);
            } else if (args[0][i] == 'F')
                  key += KEY_F(atoi(args[0]+i+1));
            else if (args[0][i] == '^')
                  key += CTRL(toupper(args[0][i+1]));
            else if ((args[0][i] == 'C') && (args[0][i+1] == '-'))
                  key += CTRL(toupper(args[0][i+2]));
      }

      if (argc == 1) {
            const char  *binding = conio_bind_get_pretty(key);

            if (binding != NULL)
                  echof(conn, NULL, "Key %i (%s) bound to ``%s''\n",
                        key, args[0], binding);
            else
                  echof(conn, NULL, "Key %i (%s) is unbound.\n",
                        key, args[0]);
      } else {
            if (args[1][0] == ':') {
                  char  buf[20];

                  for (i = 0; i < sizeof(conio_key_names)/sizeof(*conio_key_names); i++)
                        if (strcasecmp(args[1]+1, conio_key_names[i].name+1) == 0)
                              break;
                  snprintf(buf, sizeof(buf), ":%i", i);
                  conio_bind_set(key, buf, NULL);
            } else
                  conio_bind_set(key, args[1], NULL);
            echof(conn, NULL, "Key %i (%s) now bound to ``%s''\n",
                  key, args[0], args[1]);
      }
}

CONIOFUNC(alias) {
CONIODESC(Create a new command alias)
CONIOAREQ(string,commandname)
CONIOAREQ(string,script)
      script_makealias(args[0], args[1]);
      echof(conn, NULL, "Aliased <font color=\"#00FF00\">/%s</font> to: %s\n", args[0], args[1]);
}

CONIOFUNC(set) {
CONIODESC(View or change the value of a configuration or session variable; see /help settings)
CONIOAOPT(varname,varname)
CONIOAOPT(string,value)
CONIOAOPT(string,dummy)
      if (argc == 0)
            set_setvar(NULL, NULL);
      else if (argc == 1)
            set_setvar(args[0], NULL);
      else if (argc == 2)
            set_setvar(args[0], args[1]);
      else
            echof(conn, "SET", "Try <font color=\"#00FF00\">/set %s \"%s %s\"</font>.\n",
                  args[0], args[1], args[2]);
}

static void conio_listprotocols(conn_t *conn, const char *prefix) {
      int   i;
      const char *str;

      echof(conn, prefix, "Protocol drivers are currently loaded for:\n");
      for (i = 0; (str = firetalk_strprotocol(i)) != NULL; i++)
            echof(conn, prefix, "&nbsp; %s\n", str);
}

CONIOFUNC(newconn) {
CONIODESC(Open a new connection window)
CONIOAOPT(string,label)
CONIOAOPT(string,protocol)
      int   i, proto;
      conn_t      *newconn = curconn;
      const char *protostr;

      if (argc == 0) {
            conio_listprotocols(conn, NULL);
            echof(conn, NULL, "See <font color=\"#00FF00\">/help newconn</font> for more help.\n");
            return;
      } else if (argc == 1) {
            if ((strcasecmp(args[0], "Undernet") == 0)
                  || (strcasecmp(args[0], "EFnet") == 0))
                  protostr = "IRC";
            else if ((strcasecmp(args[0], "AIM") == 0)
                  || (strcasecmp(args[0], "ICQ") == 0))
                  protostr = "TOC";
            else if (strcasecmp(args[0], "lily") == 0)
                  protostr = "SLCP";
            else {
                  echof(conn, "NEWCONN", "Please supply a protocol name:");
                  conio_listprotocols(conn, "NEWCONN");
                  echof(conn, "NEWCONN", "See <font color=\"#00FF00\">/help newconn</font> for more help.\n");
                  return;
            }
      } else {
            if ((strcasecmp(args[1], "AIM") == 0)
                  || (strcasecmp(args[1], "AIM/TOC") == 0)
                  || (strcasecmp(args[1], "ICQ") == 0)
                  || (strcasecmp(args[1], "ICQ/TOC") == 0))
                  protostr = "TOC";
            else if (strcasecmp(args[1], "Lily") == 0)
                  protostr = "SLCP";
            else
                  protostr = args[1];
      }

      if ((proto = firetalk_find_protocol(protostr)) == -1) {
            echof(conn, "NEWCONN", "Invalid protocol %s.", protostr);
            conio_listprotocols(conn, "NEWCONN");
            echof(conn, "NEWCONN", "See <font color=\"#00FF00\">/help newconn</font> for more help.\n");
            return;
      }

      if (newconn != NULL)
            do {
                  if (strcasecmp(args[0], newconn->winname) == 0) {
                        echof(conn, "NEWCONN", "A window for connection %s (%s) is already open.\n",
                              newconn->winname, firetalk_strprotocol(newconn->proto));
                        return;
                  }
            } while ((newconn = newconn->next) != curconn);

      newconn = naim_newconn(proto);
      assert(newconn != NULL);

      nw_newwin(&(newconn->nwin));
      nw_initwin(&(newconn->nwin), cCONN);
      nw_erase(&(newconn->nwin));
      for (i = 0; i < faimconf.wstatus.pady; i++)
            nw_printf(&(newconn->nwin), 0, 0, "\n");
      if (curconn == NULL)
            newconn->next = newconn;
      else {
            newconn->next = curconn->next;
            curconn->next = newconn;
      }
      curconn = newconn;
      STRREPLACE(newconn->winname, args[0]);
      if (newconn->next != newconn) {
            echof(newconn, NULL, "A new connection of type %s has been created.\n",
                  firetalk_strprotocol(proto));
            echof(newconn, NULL, "Ins and Del will switch between connections, and /jump (^N, M-n, F8) will jump across connections if there are no active windows in the current one.\n");
            echof(newconn, NULL, "You can now <font color=\"#00FF00\">/connect &lt;name&gt; [&lt;server&gt;]</font> to log on.\n");
      }
      bupdate();
}

CONIOFUNC(delconn) {
CONIODESC(Close a connection window)
CONIOAOPT(string,label)
      if (curconn->next == curconn) {
            echof(conn, "DELCONN", "You must always have at least one connection open at all times.\n");
            return;
      }

      if (argc > 0) {
            int   found = 0;

            do {
                  if (strcasecmp(args[0], conn->winname) == 0) {
                        found = 1;
                        break;
                  }
            } while ((conn = conn->next) != curconn);

            if (found == 0) {
                  echof(conn, "DELCONN", "Unable to find connection %s.\n",
                        args[0]);
                  return;
            }
      }

      do_delconn(conn);
}

CONIOFUNC(server) {
CONIODESC(Connect to a service)
CONIOAOPT(string,server)
CONIOAOPT(int,port)
      const char  *na[3];

      if (argc == 0) {
            if (conn->port != 0)
                  echof(conn, NULL, "Current server: %s %i\n",
                        (conn->server == NULL)?"(default host)":conn->server,
                        conn->port);
            else
                  echof(conn, NULL, "Current server: %s (default port)\n",
                        (conn->server == NULL)?"(default host)":conn->server);
            return;
      }
      if (conn->sn == NULL) {
            echof(conn, "SERVER", "Please try to <font color=\"#00FF00\">/%s:connect</font> first.\n",
                  conn->winname);
            return;
      }
      na[0] = conn->sn;
      na[1] = args[0];
      if (argc == 2);
            na[2] = args[1];
      conio_connect(conn, argc+1, na);
}

CONIOFUNC(disconnect) {
CONIODESC(Disconnect from a server)
      if (conn->online == 0)
            echof(conn, "DISCONNECT", "You aren't connected.\n");
      else if (firetalk_disconnect(conn->conn) == FE_SUCCESS) {
            bclearall(conn, 0);
            echof(conn, NULL, "You are now disconnected.\n");
      }
      conn->online = 0;
}

CONIOFUNC(winlist) {
CONIODESC(Switch the window list window between always visible or always hidden or auto-hidden)
CONIOAOPT(string,visibility)
      if (argc == 0) {
            if (changetime == -1)
                  echof(conn, NULL, "Window list window is always hidden (possible values are HIDDEN, VISIBLE, or AUTO).\n");
            else if (changetime == 0)
                  echof(conn, NULL, "Window list window is always visible (possible values are HIDDEN, VISIBLE, or AUTO).\n");
            else
                  echof(conn, NULL, "Window list window is auto-hidden (possible values are HIDDEN, VISIBLE, or AUTO).\n");
      } else {
            if (strncasecmp(args[0], "hid", sizeof("hid")-1) == 0) {
                  if (changetime != -1) {
                        echof(conn, NULL, "Window list window is now always hidden.\n");
                        changetime = -1;
                  }
            } else if (strncasecmp(args[0], "vis", sizeof("vis")-1) == 0) {
                  if (changetime != 0) {
                        echof(conn, NULL, "Window list window is now always visible.\n");
                        changetime = 0;
                  }
            } else if ((changetime == -1) || (changetime == 0)) {
                  echof(conn, NULL, "Window list window is now auto-hidden.\n");
                  changetime = nowf;
            } else
                  echof(conn, "WINLIST", "Hmm? Possible values are HIDDEN, VISIBLE, or AUTO.\n");
      }
}

#ifdef HAVE_WORKING_FORK
static void
      exec_read(int _i, int fd, void *_buf, int _buflen) {
      conn_t      *conn = (conn_t *)_buf;
      char  buf[1024], *ptr, *n;
      int   i, buflen = sizeof(buf),
            sayit = _buflen;

      i = read(fd, buf, buflen-1);
      if (i == 0) {
            close(fd);
            mod_fd_unregister(_i);
            return;
      }
      buf[i] = 0;
      ptr = buf;
      while ((n = strchr(ptr, '\n')) != NULL) {
            *n = 0;
            if (*(n-1) == '\r')
                  *(n-1) = 0;
            if ((sayit == 0) || (conn->curbwin == NULL))
                  echof(conn, "_", "%s\n", ptr);
            else {
                  char  buf2[1024];

                  snprintf(buf2, sizeof(buf2), "/m \"%s\" %s",
                        conn->curbwin->winname, ptr);
                  conio_handleline(buf2);
            }
            ptr = n+1;
      }
      if (*ptr != 0) {
            if ((sayit == 0) || (conn->curbwin == NULL))
                  echof(conn, "_", "%s\n", ptr);
            else {
                  char  buf2[1024];

                  snprintf(buf2, sizeof(buf2), "/m \"%s\" %s",
                        conn->curbwin->winname, ptr);
                  conio_handleline(buf2);
            }
      }
}

CONIOFUNC(exec) {
CONIODESC(Execute a shell command; as in /exec -o uname -a)
CONIOAREQ(string,command)
      int   sayit = (strncmp(args[0], "-o ", 3) == 0),
            pi[2];
      pid_t pid;

      if (pipe(pi) != 0) {
            echof(conn, "EXEC", "Error creating pipe: %s.\n",
                  strerror(errno));
            return;
      }
      if ((pid = fork()) == -1) {
            echof(conn, "EXEC", "Error in fork: %s, closing pipe.\n",
                  strerror(errno));
            close(pi[0]);
            close(pi[1]);
      } else if (pid > 0) {
            close(pi[1]);
            mod_fd_register(pi[0], (O_RDONLY+1), (char *)conn, sayit,
                  exec_read);
      } else {
            char  *exec_args[] = { "/bin/sh", "-c", NULL, NULL };

            close(pi[0]);
            close(STDIN_FILENO);
            close(STDERR_FILENO);
            dup2(pi[1], STDOUT_FILENO);
            dup2(pi[1], STDERR_FILENO);
            if (sayit)
                  exec_args[2] = strdup(args[0]+3);
            else
                  exec_args[2] = strdup(args[0]);
            execvp(exec_args[0], exec_args);
            /* NOTREACHED */
            printf("%s\n", strerror(errno));
            free(exec_args[2]);
            exit(0);
      }
}
#endif

#ifdef ALLOW_DETACH
CONIOFUNC(detach) {
CONIODESC(Disconnect from the current session)
      if (sty == NULL)
            echof(conn, "DETACH", "You can only <font color=\"#00FF00\">/detach</font> when naim is run under screen.\n");
      else {
            echof(conn, NULL, "Type ``screen -r'' to re-attach.\n");
            statrefresh();
            doupdate();
            kill(getppid(), SIGHUP);
            echof(conn, NULL, "Welcome back to naim.\n");
      }
}
#endif


static int  modlist_filehelper(const char *path, lt_ptr data) {
      conn_t      *conn = (conn_t *)data;
      const char *filename = naim_basename(path);

      if (strncmp(filename, "lib", 3) == 0)
            return(0);
      if (strstr(path, "/naim/") == NULL)
            return(0);
      echof(conn, NULL, "&nbsp; <font color=\"#FF0000\">%s</font> (<font color=\"#808080\">%s</font>)\n", filename, path);
      return(0);
}

static int  modlist_helper(lt_dlhandle mod, lt_ptr data) {
      conn_t      *conn = (conn_t *)data;
      const lt_dlinfo
            *dlinfo = lt_dlgetinfo(mod);
      char  *tmp;
      double      *ver;

      if (dlinfo->ref_count > 0) {
            assert(dlinfo->ref_count == 1);
            echof(conn, NULL, "&nbsp; <font color=\"#FF0000\">%s</font> (<font color=\"#808080\">%s</font>)\n",
                  dlinfo->name, dlinfo->filename);
            if ((tmp = lt_dlsym(mod, "module_category")) != NULL)
                  echof(conn, NULL, "&nbsp; &nbsp; &nbsp; &nbsp;Category: <font color=\"#FFFFFF\">%s</font>\n", tmp);
            if ((tmp = lt_dlsym(mod, "module_description")) != NULL)
                  echof(conn, NULL, "&nbsp; &nbsp; Description: <font color=\"#FFFFFF\">%s</font>\n", tmp);
            if ((tmp = lt_dlsym(mod, "module_license")) != NULL)
                  echof(conn, NULL, "&nbsp; &nbsp; &nbsp; &nbsp; License: <font color=\"#FFFFFF\">%s</font>\n", tmp);
            if ((tmp = lt_dlsym(mod, "module_author")) != NULL)
                  echof(conn, NULL, "&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;Author: <font color=\"#FFFFFF\">%s</font>\n", tmp);
            if (((ver = lt_dlsym(mod, "module_version")) != NULL) && (*ver >= 1.0))
                  echof(conn, NULL, "&nbsp; &nbsp; &nbsp; &nbsp;Code Age: <font color=\"#FFFFFF\">%s</font>\n", dtime(nowf - *ver));
      }

      return(0);
}

CONIOFUNC(dlsym) {
CONIOAREQ(string,symbol)
      lt_dlhandle mod;
      lt_ptr      ptr;

#ifdef DLOPEN_SELF_LIBNAIM_CORE
      mod = lt_dlopen("cygnaim_core-0.dll");
#else
      mod = lt_dlopen(NULL);
#endif
      ptr = lt_dlsym(mod, args[0]);
      if (ptr != NULL)
            echof(conn, NULL, "Symbol %s found at %#p.\n", args[0], ptr);
      else
            echof(conn, "DLSYM", "Symbol %s not found.\n", args[0]);
      lt_dlclose(mod);
}

CONIOFUNC(modlist) {
CONIODESC(Search for and list all potential and resident naim modules)
      echof(conn, NULL, "Modules found in the default search path:\n");
      lt_dlforeachfile(NULL, modlist_filehelper, conn);
      echof(conn, NULL, "Additional modules can be loaded using their explicit paths, as in <font color=\"#00FF00\">/modload %s/mods/mymod.la</font>.\n",
            home);
      echof(conn, NULL, "Modules currently resident:\n");
      lt_dlforeach(modlist_helper, conn);
      echof(conn, NULL, "See <font color=\"#00FF00\">/help modload</font> for more information.\n");
}

CONIOFUNC(modload) {
CONIODESC(Load and initialize a dynamic module)
CONIOAREQ(filename,module)
CONIOAOPT(string,options)
      lt_dlhandle mod;
      const lt_dlinfo
            *dlinfo;
      int   (*naim_init)(lt_dlhandle mod, const char *str);
      const char
            *options;

      if (argc > 1)
            options = args[1];
      else
            options = NULL;

      mod = lt_dlopenext(args[0]);
      if (mod == NULL) {
            echof(conn, "MODLOAD", "Unable to load module <font color=\"#FF0000\">%s</font>: %s.\n",
                  args[0], lt_dlerror());
            return;
      }
      dlinfo = lt_dlgetinfo(mod);
      assert(dlinfo->ref_count > 0);
      if (dlinfo->ref_count > 1) {
            echof(conn, NULL, "Module <font color=\"#FF0000\">%s</font> (<font color=\"#808080\">%s</font>) already loaded.\n",
                  dlinfo->name, dlinfo->filename);
            lt_dlclose(mod);
            return;
      }
      echof(conn, NULL, "Module <font color=\"#FF0000\">%s</font> (<font color=\"#808080\">%s</font>) loaded.\n",
            dlinfo->name, dlinfo->filename);

      naim_init = lt_dlsym(mod, "naim_init");
      if (naim_init != NULL) {
            if (naim_init(mod, options) != MOD_FINISHED)
                  echof(conn, NULL, "Module <font color=\"#FF0000\">%s</font> initialized, leaving resident.\n",
                        dlinfo->name);
            else {
                  echof(conn, NULL, "Module <font color=\"#FF0000\">%s</font> initialized, unloading.\n",
                        dlinfo->name);
                  echof(conn, NULL, "Module <font color=\"#FF0000\">%s</font> (<font color=\"#808080\">%s</font>) unloaded.\n",
                        dlinfo->name, dlinfo->filename);
                  lt_dlclose(mod);
            }
      } else
            echof(conn, NULL, "Module <font color=\"#FF0000\">%s</font> has no initializer (<font color=\"#FF0000\">%s</font>:naim_init()): %s, leaving resident.\n",
                  dlinfo->name, args[0], lt_dlerror());
}

CONIOFUNC(modunload) {
CONIODESC(Deinitialize and unload a resident module)
CONIOAREQ(string,module)
      lt_dlhandle mod;

      for (mod = lt_dlhandle_next(NULL); mod != NULL; mod = lt_dlhandle_next(mod)) {
            const lt_dlinfo
                  *dlinfo = lt_dlgetinfo(mod);

            if ((dlinfo->name != NULL) && (strcasecmp(args[0], dlinfo->name) == 0)) {
                  int   (*naim_exit)(lt_dlhandle mod, const char *str);

                  naim_exit = lt_dlsym(mod, "naim_exit");
                  if (naim_exit != NULL) {
                        if (naim_exit(mod, args[0]) != MOD_FINISHED) {
                              echof(conn, "MODUNLOAD", "Module <font color=\"#FF0000\">%s</font> is busy (<font color=\"#FF0000\">%s</font>:naim_exit() != MOD_FINISHED), leaving resident.\n",
                                    dlinfo->name, dlinfo->name);
                              return;
                        } else
                              echof(conn, NULL, "Module <font color=\"#FF0000\">%s</font> deinitialized, unloading.\n",
                                    dlinfo->name);
                  }
                  echof(conn, NULL, "Module <font color=\"#FF0000\">%s</font> (<font color=\"#808080\">%s</font>) unloaded.\n",
                        dlinfo->name, dlinfo->filename);
                  lt_dlclose(mod);
                  return;
            }
      }
      echof(conn, "MODUNLOAD", "No module named %s loaded.\n", args[0]);
}

CONIOFUNC(resize) {
CONIODESC(Resize all windows)
CONIOAOPT(int,height)
      char  buf[20];
      int   scrollback;

      if (argc == 0)
            scrollback = secs_getvar_int("scrollback");
      else
            scrollback = atoi(args[0]);

      if (scrollback < (LINES-1)) {
            echof(conn, "RESIZE", "Window height (%i) must be greater than %i.", scrollback, LINES-2);
            scrollback = LINES-1;
      } else if (scrollback > 5000) {
            echof(conn, "RESIZE", "Window height (%i) must be less than 5001.", scrollback);
            scrollback = 5000;
      }

      snprintf(buf, sizeof(buf), "%i", scrollback);
      secs_setvar("scrollback", buf);

      faimconf.wstatus.pady = scrollback;
      win_resize();
      echof(conn, NULL, "Windows resized.");
}

//extern void     (*conio_status)();
CONIOFUNC(status) {
CONIODESC(Connection status report)
CONIOAOPT(string,connection)
      buddywin_t  *bwin;
      conn_t      *c;
      int   discussions = 0,
            users = 0,
            files = 0;

      if (argc >= 0) {
            extern char naimrcfilename[];
            extern time_t startuptime;

            echof(conn, NULL, "Running " PACKAGE_STRING NAIM_SNAPSHOT " for %s.\n",
                  dtime(now - startuptime));
            echof(conn, NULL, "Config file: %s\n", naimrcfilename);
      }

      if (argc == 0) {
            const char *args[] = { conn->winname };

            conio_status(conn, -1, args);
            for (c = conn->next; c != conn; c = c->next) {
                  args[0] = c->winname;
                  echof(conn, NULL, "-");
                  conio_status(conn, -1, args);
            }
            echof(conn, NULL, "See <font color=\"#00FF00\">/help status</font> for more information.\n");
            return;
      }
      if (strcasecmp(args[0], conn->winname) != 0) {
            for (c = conn->next; c != conn; c = c->next)
                  if (strcasecmp(args[0], c->winname) == 0)
                        break;
            if (c == conn) {
                  echof(conn, "STATUS", "Unknown connection %s.\n", args[0]);
                  return;
            }
      } else
            c = conn;

      echof(conn, NULL, "Information for %s:\n", c->winname);
      if (c->online > 0) {
            assert(c->sn != NULL);
            echof(conn, NULL, "Online as %s for %s.\n", c->sn, dtime(now - c->online));
      } else {
            if (c->sn != NULL)
                  echof(conn, NULL, "Not online (want to be %s)\n", c->sn);
            else
                  echof(conn, NULL, "Not online.\n");
      }

      if ((bwin = c->curbwin) != NULL) {
        echof(conn, NULL, "Windows:\n");
        do {
            char  *type = NULL,
                  *close;

            if (bwin->keepafterso)
                  close = " OPEN_INDEFINITELY";
            else
                  close = " QUICK_CLOSE";
            switch (bwin->et) {
              case CHAT:
                  discussions++;
                  type = "DISCUSSION_WINDOW";
                  break;
              case BUDDY:
                  users++;
                  type = "USER_WINDOW";
                  if (!USER_PERMANENT(bwin->e.buddy))
                        close = " SLOW_CLOSE";
                  break;
              case TRANSFER:
                  files++;
                  type = "TRANSFER_WINDOW";
                  break;
            }
            if ((bwin->viewtime > 0.0) && ((nowf - bwin->viewtime) > 2.0))
                  echof(conn, NULL, "&nbsp; <font color=\"#00FFFF\">%s</font> (%s%s%s%s) [last viewed %s]\n", bwin->winname,
                        type, close,
                        bwin->waiting?" LIVE_MESSAGES_WAITING":"",
                        bwin->pouncec?" QUEUED_MESSAGES_WAITING":"",
                        dtime(nowf - bwin->viewtime));
            else
                  echof(conn, NULL, "&nbsp; <font color=\"#00FFFF\">%s</font> (%s%s%s%s)\n", bwin->winname,
                        type, close,
                        bwin->waiting?" LIVE_MESSAGES_WAITING":"",
                        bwin->pouncec?" QUEUED_MESSAGES_WAITING":"");
        } while ((bwin = bwin->next) != c->curbwin);
      }
      echof(conn, NULL, "%i discussions, %i users, %i file transfers.\n", discussions, users, files);
      if (c == conn)
            echof(conn, NULL, "See <font color=\"#00FF00\">/names</font> for buddy list information.\n");
      else
            echof(conn, NULL, "See <font color=\"#00FF00\">/%s:names</font> for buddy list information.\n", c->winname);
}



static int
      cmd_unknown(conn_t *c, const char *cmd, int argc, const char **args) {
      echof(c, cmd, "Unknown command.\n");
      return(HOOK_STOP);
}

void  conio_hook_init(void) {
      void  *mod = NULL;

      HOOK_ADD(getcmd, mod, cmd_unknown, 1000);
}

HOOK_DECLARE(getcmd);
void  conio_handlecmd(const char *buf) {
      conn_t      *c = NULL;
      char  line[1024], *cmd, *arg, *tmp;
      const char  *args[CONIO_MAXPARMS];
      int   i, a = 0;

      assert(buf != NULL);

      while (*buf == '/')
            buf++;

      if (*buf == 0)
            return;

      strncpy(line, buf, sizeof(line)-1);
      line[sizeof(line)-1] = 0;

      while ((strlen(line) > 0) && isspace(line[strlen(line)-1]))
            line[strlen(line)-1] = 0;

      if (*line == 0)
            return;

      cmd = atom(line);
      arg = firstwhite(line);

      if ((tmp = strchr(cmd, ':')) != NULL) {
            conn_t      *conn = curconn;

            *tmp = 0;
            if (conn != NULL)
                  do {
                        if (strcasecmp(cmd, conn->winname) == 0) {
                              c = conn;
                              break;
                        }
                  } while ((conn = conn->next) != curconn);
            if (c == NULL) {
                  echof(curconn, NULL, "[%s:%s] Unknown connection.\n", cmd, tmp+1);
                  return;
            }
            cmd = tmp+1;
      } else
            c = curconn;

      if (script_doalias(cmd, arg) == 1)
            return;

      for (i = 0; i < cmdc; i++)
            if (strcasecmp(cmdar[i].c, cmd) == 0)
                  break;
            else {
                  int   j;

                  for (j = 0; cmdar[i].aliases[j] != NULL; j++)
                        if (strcasecmp(cmdar[i].aliases[j], cmd) == 0)
                              break;
                  if (cmdar[i].aliases[j] != NULL)
                        break;
            }
      if (i == cmdc) {
            HOOK_CALL(getcmd, (c, cmd, a, args));
            return;
      }
      assert(cmdar[i].maxarg <= CONIO_MAXPARMS);

      if ((cmdar[i].where != C_STATUS) && (cmdar[i].where != C_ANYWHERE) && !inconn) {
            if (cmdar[i].where == C_INCHAT)
                  echof(c, cmdar[i].c, "You must be in a chat.\n");
            else if (cmdar[i].where == C_INUSER)
                  echof(c, cmdar[i].c, "You must be in a query.\n");
            else
                  echof(c, cmdar[i].c, "You can not be in status.\n");
            return;
      } else if ((cmdar[i].where == C_STATUS) && inconn) {
            assert(curconn->curbwin != NULL);
            echof(c, cmdar[i].c, "You must be in status.\n");
            return;
      } else if ((cmdar[i].where == C_INUSER) && (c->curbwin->et == CHAT)) {
            echof(c, cmdar[i].c, "You must be in a query.\n");
            return;
      } else if ((cmdar[i].where == C_INCHAT) && (c->curbwin->et != CHAT)) {
            echof(c, cmdar[i].c, "You must be in a chat.\n");
            return;
      }

      for (a = 0; (a < cmdar[i].maxarg) && (arg != NULL); a++) {
            args[a] = atom(arg);
            if (a < cmdar[i].maxarg-1)
                  arg = firstwhite(arg);
      }

      if (a < cmdar[i].minarg) {
            echof(c, cmdar[i].c, "Command requires at least %i arguments.\n",
                  cmdar[i].minarg);
            return;
      }

      cmdar[i].func(c, a, args);
}

void  (*secs_client_cmdhandler)(const char *) = conio_handlecmd;

void  conio_handleline(const char *line) {
      if (*line == '/')
            conio_handlecmd(line+1);
      else if (!inconn)
            conio_handlecmd(line);
      else {
            const char  *args[] = { NULL, line };

            curconn->curbwin->keepafterso = 1;
            conio_msg(curconn, 2, args);
      }
}

static const char
      *n_strnrchr(const char *const str, const char c, const int len) {
      const char
            *s = str+len;

      while (s >= str) {
            if (*s == c)
                  return(s);
            s--;
      }
      return(NULL);
}

#define ADDTOBUF(ch)    do { \
      if (strlen(buf) < sizeof(buf)-1) { \
            if (buf[bufloc] != 0) \
                  memmove(buf+bufloc+1, buf+bufloc, sizeof(buf)-bufloc-1); \
            buf[bufloc++] = (ch); \
            histpos = histc-1; \
            STRREPLACE(histar[histpos], buf); \
      } else \
            beep(); \
} while (0)

const char
      *window_tabcomplete(conn_t *const conn, const char *start, const char *buf, const int bufloc, int *const match, const char **desc) {
      static char
            str[1024];
      buddywin_t
            *bwin;
      size_t      startlen = strlen(start);

      if ((bwin = conn->curbwin) == NULL)
            return(NULL);
      bwin = bwin->next;
      do {
            if (aimncmp(bwin->winname, start, startlen) == 0) {
                  char  *name = bwin->winname;
                  int   j;

                  if (match != NULL)
                        *match = bufloc - (start-buf);
                  if (desc != NULL) {
                        if (bwin->et == BUDDY)
                              *desc = bwin->e.buddy->_name;
                        else
                              *desc = NULL;
                  }

                  for (j = 0; (j < sizeof(str)-1) && (*name != 0); j++) {
                        while (*name == ' ')
                              name++;
                        str[j] = *(name++);
                  }
                  str[j] = 0;
                  return(str);
            }
            if ((bwin->et == BUDDY) && (bwin->e.buddy->_name != NULL) && (aimncmp(bwin->e.buddy->_name, start, startlen) == 0)) {
                  char  *name = bwin->e.buddy->_name;
                  int   j;

                  if (match != NULL)
                        *match = bufloc - (start-buf);
                  if (desc != NULL)
                        *desc = bwin->winname;

                  for (j = 0; (j < sizeof(str)-1) && (*name != 0); j++) {
                        while (*name == ' ')
                              name++;
                        str[j] = *(name++);
                  }
                  str[j] = 0;
                  return(str);
            }
      } while ((bwin = bwin->next) != conn->curbwin->next);

      return(NULL);
}

const char
      *chat_tabcomplete(conn_t *const conn, const char *start, const char *buf, const int bufloc, int *const match, const char **desc) {
      static char
            str[1024];
      buddywin_t
            *bwin;
      size_t      startlen = strlen(start);

      if ((bwin = conn->curbwin) == NULL)
            return(NULL);
      bwin = bwin->next;
      do {
            if ((bwin->et == CHAT) && (aimncmp(bwin->winname, start, startlen) == 0)) {
                  char  *name = bwin->winname;
                  int   j;

                  if (match != NULL)
                        *match = bufloc - (start-buf);
                  if (desc != NULL)
                        *desc = NULL;

                  for (j = 0; (j < sizeof(str)-1) && (*name != 0); j++) {
                        while (*name == ' ')
                              name++;
                        str[j] = *(name++);
                  }
                  str[j] = 0;
                  return(str);
            }
      } while ((bwin = bwin->next) != conn->curbwin->next);

      return(NULL);
}

const char
      *filename_tabcomplete(conn_t *const conn, const char *start, const char *buf, const int bufloc, int *const match, const char **desc) {
      static char
            str[1024];
      struct dirent
            *dire;
      DIR   *dir;
      const char
            *end;
      size_t      startlen = strlen(start),
            endlen;

      if ((dir = opendir(start)) == NULL) {
            if ((end = strrchr(start, '/')) != NULL) {
                  char  buf[1024];

                  snprintf(buf, sizeof(buf), "%.*s", end-start+1, start);
                  dir = opendir(buf);
            } else {
                  end = start-1;
                  dir = opendir(".");
            }
      } else
            end = start+startlen;

      if (dir == NULL)
            return(NULL);

      endlen = strlen(end+1);
      while ((dire = readdir(dir)) != NULL) {
            if ((strcmp(dire->d_name, ".") == 0) || (strcmp(dire->d_name, "..") == 0))
                  continue;
            if ((*end == 0) || (strncmp(dire->d_name, end+1, endlen) == 0)) {
                  struct stat
                        statbuf;

                  if (((end-start) > 0) && (start[end-start-1] == '/'))
                        snprintf(str, sizeof(str)-1, "%.*s%s", end-start, start, dire->d_name);
                  else if (end == (start-1))
                        snprintf(str, sizeof(str)-1, "%s", dire->d_name);
                  else
                        snprintf(str, sizeof(str)-1, "%.*s/%s", end-start, start, dire->d_name);
                  if (stat(str, &statbuf) == -1)
                        continue;
                  if (S_ISDIR(statbuf.st_mode))
                        strcat(str, "/");
                  if (match != NULL)
                        *match = bufloc-(start-buf);
                  if (desc != NULL)
                        *desc = NULL;
                  closedir(dir);
                  return(str);
            }
      }
      closedir(dir);
      return(NULL);
}

const char
      *conio_tabcomplete(const char *buf, const int bufloc, int *const match, const char **desc) {
      char  *sp = memchr(buf, ' ', bufloc);

      assert(*buf == '/');

      if (sp == NULL) {
            extern script_t
                  *scriptar;
            extern int
                  scriptc;
            conn_t      *conn;
            const char
                  *co = memchr(buf, ':', bufloc);
            int   i;

            if (co == NULL)
                  co = buf+1;
            else
                  co++;

            for (i = 0; i < scriptc; i++)
                  if (strncasecmp(scriptar[i].name, co, bufloc-(co-buf)) == 0) {
                        if (match != NULL)
                              *match = bufloc-(co-buf);
                        if (desc != NULL)
                              *desc = scriptar[i].script;
                        return(scriptar[i].name);
                  }

            for (i = 0; i < cmdc; i++)
                  if (strncasecmp(cmdar[i].c, co, bufloc-(co-buf)) == 0) {
                        if (match != NULL)
                              *match = bufloc-(co-buf);
                        if (desc != NULL)
                              *desc = cmdar[i].desc;
                        return(cmdar[i].c);
                  } else {
                        int   j;

                        for (j = 0; cmdar[i].aliases[j] != NULL; j++)
                              if (strncasecmp(cmdar[i].aliases[j], co, bufloc-(co-buf)) == 0) {
                                    if (match != NULL)
                                          *match = bufloc-(co-buf);
                                    if (desc != NULL)
                                          *desc = cmdar[i].desc;
                                    return(cmdar[i].aliases[j]);
                              }
                  }

            conn = curconn->next;
            do {
                  if (strncasecmp(conn->winname, co, bufloc-(co-buf)) == 0) {
                        static char
                              str[1024];

                        if (match != NULL)
                              *match = bufloc-(co-buf);
                        if (desc != NULL)
                              *desc = firetalk_strprotocol(conn->proto);
                        strncpy(str, conn->winname, sizeof(str)-2);
                        str[sizeof(str)-2] = 0;
                        strcat(str, ":");
                        return(str);
                  }
            } while ((conn = conn->next) != curconn->next);

            return(NULL);
      } else {
            conn_t      *conn = curconn;
            const char
                  *sp = memchr(buf, ' ', bufloc),
                  *co = memchr(buf, ':', bufloc),
                  *start = n_strnrchr(buf, ' ', bufloc)+1;
            char  type;
            int   cmd;

            if ((co != NULL) && (co < sp)) {
                  size_t      connlen = (co - buf)-1;

                  do {
                        if ((strncasecmp(buf+1, conn->winname, connlen) == 0) && (conn->winname[connlen] == 0))
                              break;
                  } while ((conn = conn->next) != curconn);
                  co++;
            } else
                  co = buf+1;

            for (cmd = 0; cmd < cmdc; cmd++)
                  if ((strncasecmp(cmdar[cmd].c, co, sp-co) == 0) && (cmdar[cmd].c[sp-co] == 0))
                        break;
                  else {
                        int   alias;

                        for (alias = 0; cmdar[cmd].aliases[alias] != NULL; alias++)
                              if ((strncasecmp(cmdar[cmd].aliases[alias], co, sp-co) == 0) && (cmdar[cmd].aliases[alias][sp-co] == 0))
                                    break;
                        if (cmdar[cmd].aliases[alias] != NULL)
                              break;
                  }

            if (cmd < cmdc)
                  type = cmdar[cmd].args[0].type;
            else
                  type = 's';

            switch (type) {
              case 'W':
                  return(window_tabcomplete(conn, start, buf, bufloc, match, desc));
              case 'B':
                  return(buddy_tabcomplete(conn, start, buf, bufloc, match, desc));
              case 'C':
                  return(chat_tabcomplete(conn, start, buf, bufloc, match, desc));
              case 'F':
                  return(filename_tabcomplete(conn, start, buf, bufloc, match, desc));
              case 'V':
                  return(set_tabcomplete(conn, start, buf, bufloc, match, desc));
              case 'E': {
                        const char *ret = buddy_tabcomplete(conn, start, buf, bufloc, match, desc);

                        if (ret != NULL)
                              return(ret);
                  }
                  return(chat_tabcomplete(conn, start, buf, bufloc, match, desc));
              case 's': case 'i': case 'b': case '?':
              case -1:
                  return(NULL);
              default:
                  abort();
            }
      }
}

static void
      gotkey_real(int c) {
      static char **histar = NULL;
      static int  histc = 0,
                  histpos = 0;
      static int  bufloc = 0,
                  bufmatchpos = -1;
      static char buf[1024] = { 0 },
                  inwhite = 0;
      const char  *binding;
      void  (*bindfunc)(char *, int *);

      if (histar == NULL) {
            histpos = 0;
            histc = 1;
            histar = realloc(histar, histc*sizeof(*histar));
            histar[histpos] = NULL;

            conio_bind_defaults();
            conio_filter_defaults();
            return;
      }
      if (c == 0)
            return;

      if ((c == '\b') || (c == 0x7F))
            c = KEY_BACKSPACE;
      if ((c == '\r') || (c == '\n'))
            c = KEY_ENTER;
      if (c == CTRL('[')) {
            int   k;

            k = getch();
            if (k != ERR) {
                  char  buf[256];

                  snprintf(buf, sizeof(buf), "KEY_MAX plus %s", keyname(k));
                  secs_setvar("lastkey", buf);
                  c = KEY_MAX + k;
            } else
                  secs_setvar("lastkey", keyname(c));
      } else
            secs_setvar("lastkey", keyname(c));

      binding = conio_bind_get(c);
      bindfunc = conio_bind_func(c);
      if ((binding != NULL) || (bindfunc != NULL)) {
            if ((binding != NULL) && (binding[0] == ':')) {
                int     bindid = atoi(binding+1);

                if (bindid != CONIO_KEY_SPACE_OR_NBSP)
                  inwhite = 0;
                if ((bindid != CONIO_KEY_INPUT_SCROLL_BACK) && (bindid != CONIO_KEY_INPUT_SCROLL_FORWARD))
                  bufmatchpos = -1;
                switch(bindid) {
                  case CONIO_KEY_REDRAW: /* Redraw the screen */
                        doredraw = 1;
                        break;
                  case CONIO_KEY_INPUT_SCROLL_BACK: /* Search back through your input history */
                        if (histpos > 0) {
                              int   tmp;

                              if (bufmatchpos == -1)
                                    bufmatchpos = bufloc;
                              for (tmp = histpos-1; tmp >= 0; tmp--)
                                    if (strncasecmp(histar[tmp], buf, bufmatchpos) == 0) {
                                          assert(histar[tmp] != NULL);
                                          if (buf[bufloc] == 0)
                                                bufloc = strlen(histar[tmp]);
                                          strncpy(buf, histar[tmp], sizeof(buf)-1);
                                          histpos = tmp;
                                          break;
                                    }
                        }
                        break;
                  case CONIO_KEY_INPUT_SCROLL_FORWARD: /* Search forward through your input history */
                        if (histpos < histc) {
                              int   tmp;

                              if (bufmatchpos == -1)
                                    bufmatchpos = bufloc;
                              for (tmp = histpos+1; tmp < histc; tmp++)
                                    if (histar[tmp] == NULL) {
                                          assert(tmp == histc-1);
                                          memset(buf, 0, sizeof(buf));
                                          bufloc = 0;
                                          histpos = tmp;
                                          break;
                                    } else if (strncasecmp(histar[tmp], buf, bufmatchpos) == 0) {
                                          if (buf[bufloc] == 0)
                                                bufloc = strlen(histar[tmp]);
                                          strncpy(buf, histar[tmp], sizeof(buf)-1);
                                          histpos = tmp;
                                          break;
                                    }
                        }
                        break;
                  case CONIO_KEY_INPUT_CURSOR_LEFT: /* Move left in the input line */
                        if (bufloc > 0)
                              bufloc--;
                        break;
                  case CONIO_KEY_INPUT_CURSOR_RIGHT: /* Move right in the input line */
                        if (buf[bufloc] != 0)
                              bufloc++;
                        break;
                  case CONIO_KEY_SPACE_OR_NBSP: /* In paste mode, an HTML hard space, otherwise a literal space */
                        if ((inpaste == 0) || (inwhite == 0)) {
                              ADDTOBUF(' ');
                              inwhite = 1;
                        } else {
                              ADDTOBUF('&');
                              ADDTOBUF('n');
                              ADDTOBUF('b');
                              ADDTOBUF('s');
                              ADDTOBUF('p');
                              ADDTOBUF(';');
                              inwhite = 0;
                        }
                        break;
                  case CONIO_KEY_TAB: /* An HTML tab (8 hard spaces) */
                        ADDTOBUF('&');
                        ADDTOBUF('n');
                        ADDTOBUF('b');
                        ADDTOBUF('s');
                        ADDTOBUF('p');
                        ADDTOBUF(';');
                        ADDTOBUF(' ');
                        ADDTOBUF('&');
                        ADDTOBUF('n');
                        ADDTOBUF('b');
                        ADDTOBUF('s');
                        ADDTOBUF('p');
                        ADDTOBUF(';');
                        ADDTOBUF(' ');
                        ADDTOBUF('&');
                        ADDTOBUF('n');
                        ADDTOBUF('b');
                        ADDTOBUF('s');
                        ADDTOBUF('p');
                        ADDTOBUF(';');
                        ADDTOBUF(' ');
                        ADDTOBUF('&');
                        ADDTOBUF('n');
                        ADDTOBUF('b');
                        ADDTOBUF('s');
                        ADDTOBUF('p');
                        ADDTOBUF(';');
                        ADDTOBUF('&');
                        ADDTOBUF('n');
                        ADDTOBUF('b');
                        ADDTOBUF('s');
                        ADDTOBUF('p');
                        ADDTOBUF(';');
                        break;
                  case CONIO_KEY_TAB_BUDDY_NEXT: { /* In paste mode, an HTML tab (8 hard spaces), otherwise perform command completion or advance to the next window */
                              int   temppaste = inpaste;
                              char  *ptr;

                              if (!temppaste && (buf[0] != 0) && (buf[0] != '/')) {
                                    fd_set      rfd;
                                    struct timeval    timeout;

                                    FD_ZERO(&rfd);
                                    FD_SET(STDIN_FILENO, &rfd);
                                    timeout.tv_sec = 0;
                                    timeout.tv_usec = 1;
                                    select(STDIN_FILENO+1, &rfd, NULL, NULL, &timeout);
                                    if (FD_ISSET(STDIN_FILENO, &rfd))
                                          temppaste = 1;
                              }
                              if (temppaste) {
                                    ADDTOBUF('&');
                                    ADDTOBUF('n');
                                    ADDTOBUF('b');
                                    ADDTOBUF('s');
                                    ADDTOBUF('p');
                                    ADDTOBUF(';');
                                    ADDTOBUF(' ');
                                    ADDTOBUF('&');
                                    ADDTOBUF('n');
                                    ADDTOBUF('b');
                                    ADDTOBUF('s');
                                    ADDTOBUF('p');
                                    ADDTOBUF(';');
                                    ADDTOBUF(' ');
                                    ADDTOBUF('&');
                                    ADDTOBUF('n');
                                    ADDTOBUF('b');
                                    ADDTOBUF('s');
                                    ADDTOBUF('p');
                                    ADDTOBUF(';');
                                    ADDTOBUF(' ');
                                    ADDTOBUF('&');
                                    ADDTOBUF('n');
                                    ADDTOBUF('b');
                                    ADDTOBUF('s');
                                    ADDTOBUF('p');
                                    ADDTOBUF(';');
                                    ADDTOBUF('&');
                                    ADDTOBUF('n');
                                    ADDTOBUF('b');
                                    ADDTOBUF('s');
                                    ADDTOBUF('p');
                                    ADDTOBUF(';');
                                    break;
                              } else if ((bufloc > 0) && (buf[0] == '/')
                                    && (buf[bufloc-1] != ' ')) {
                                    int   match;
                                    const char
                                          *str;

                                    if ((str = conio_tabcomplete(buf, bufloc, &match, NULL)) != NULL) {
                                          int   i;

                                          bufloc -= match;
                                          for (i = 0; i < match; i++)
                                                buf[bufloc++] = str[i];
                                          for (; str[i] != 0; i++)
                                                ADDTOBUF(str[i]);
                                          if (isalnum(str[i-1]))
                                                ADDTOBUF(' ');
                                    } else
                                          beep();

                                    break;
                              } else if ((bufloc > 0) && (buf[0] != '/')
                                    && inconn && (curconn->curbwin->et == CHAT)
                                    && (*buf != ',')
                                    && (((ptr = strchr(buf, ' ')) == NULL) || (*(ptr+1) == 0))) {
                                    int   i;

                                    namescomplete.buf = strdup(buf);
                                    namescomplete.len = bufloc;
                                    namescomplete.foundfirst = namescomplete.foundmatch = namescomplete.foundmult = 0;
                                    firetalk_chat_listmembers(curconn->conn, curconn->curbwin->winname);
                                    if (namescomplete.foundfirst && !namescomplete.foundmatch)
                                          firetalk_chat_listmembers(curconn->conn, curconn->curbwin->winname);
                                    if (namescomplete.foundfirst && !namescomplete.foundmatch)
                                          bufloc = strlen(buf);
                                    else if (namescomplete.foundmatch) {
                                          static char *lastcomplete = NULL;
                                          static int numcompletes = 0;

                                          memset(buf, 0, sizeof(buf));
                                          inwhite = inpaste = bufloc = 0;
                                          if ((lastcomplete != NULL) && (strcmp(namescomplete.buf, lastcomplete) == 0) && (getvar(curconn, "vulgarnickcompletion") == NULL)) {
                                                if (numcompletes == 0)
                                                      echof(curconn, "TAB", "Please do not nick complete the same name twice. It is likely that %s already saw your initial address and can track your messages without additional help, or %s did not see your initial message and is not going to hear your followup messages either.\n", lastcomplete, lastcomplete);
                                                else if (numcompletes == 1)
                                                      echof(curconn, "TAB", "If you find yourself sending a large number of messages to the same person in a group, it might be best to take the person aside to finish your conversation. Online, this can be accomplished by using <font color=\"#00FF00\">/msg %s yourmessage</font>.\n", lastcomplete);
                                                else if (numcompletes == 2)
                                                      echof(curconn, "TAB", "If you really want to nick complete the same name twice without this extra step, use <font color=\"#00FF00\">/set vulgarnickcompletion 1</font>. However, please try to think of how you would act if you were holding this conversation in person--including moving aside if the group area is too noisy.\n");
                                                else
                                                      echof(curconn, "TAB", "Remember: This is a group you are addressing. If your conversation is directed at only one person, it may be best to hold it in private, using <font color=\"#00FF00\">/msg</font>. If the conversation is for the benefit of the entire audience, you should not prefix every message with an individual's name.\n");
                                                numcompletes++;
                                                if (numcompletes >= 20)
                                                      numcompletes = 0;
                                                free(lastcomplete);
                                                lastcomplete = NULL;
                                          } else {
                                                for (i = 0; namescomplete.buf[i] != 0; i++)
                                                      ADDTOBUF(namescomplete.buf[i]);
                                                if (namescomplete.foundmatch) {
                                                      ADDTOBUF(',');
                                                      ADDTOBUF(' ');
                                                }
                                                if (namescomplete.foundmult)
                                                      bufloc = namescomplete.len;
                                                else
                                                      bufloc = strlen(buf);
                                                free(lastcomplete);
                                                lastcomplete = strdup(namescomplete.buf);
                                          }
                                    }
                                    free(namescomplete.buf);
                                    namescomplete.buf = NULL;
                                    namescomplete.foundfirst = namescomplete.foundmatch = namescomplete.foundmult = namescomplete.len = 0;
                                    break;
                              }
                        }
                  case CONIO_KEY_BUDDY_NEXT: /* Advance to the next window */
                        if (inconn_real) {
                              assert(curconn->curbwin != NULL);
                              curconn->curbwin = curconn->curbwin->next;
                              nw_touchwin(&(curconn->curbwin->nwin));
                              scrollbackoff = 0;
                        }
                        naim_changetime();
                        bupdate();
                        break;
                  case CONIO_KEY_BUDDY_PREV: /* Go to the previous window */
                        if (inconn_real) {
                              buddywin_t  *bbefore = curconn->curbwin->next;
      
                              while (bbefore->next != curconn->curbwin)
                                    bbefore = bbefore->next;
                              curconn->curbwin = bbefore;
                              nw_touchwin(&(bbefore->nwin));
                              scrollbackoff = 0;
                        }
                        naim_changetime();
                        bupdate();
                        break;
                  case CONIO_KEY_CONN_NEXT: /* Go to the next connection window (AIM, EFnet, etc.) */
                        curconn = curconn->next;
                        if (curconn->curbwin != NULL)
                              nw_touchwin(&(curconn->curbwin->nwin));
                        scrollbackoff = 0;
                        naim_changetime();
                        bupdate();
                        break;
                  case CONIO_KEY_CONN_PREV: { /* Go to the previous connection window */
                              conn_t      *cbefore = curconn->next;
      
                              while (cbefore->next != curconn)
                                    cbefore = cbefore->next;
                              curconn = cbefore;
                              scrollbackoff = 0;
                              if (cbefore->curbwin != NULL)
                                    nw_touchwin(&(cbefore->curbwin->nwin));
                        }
                        naim_changetime();
                        bupdate();
                        break;
                  case CONIO_KEY_INPUT_BACKSPACE: /* Delete the previous character in the input line */
                        if (bufloc < 1)
                              break;
                        memmove(buf+bufloc-1, buf+bufloc,
                              sizeof(buf)-bufloc);
                        bufloc--;
                        break;
                  case CONIO_KEY_INPUT_DELETE: /* Delete the current character in the input line */
                        memmove(buf+bufloc, buf+bufloc+1,
                              sizeof(buf)-bufloc-1);
                        break;
                  case CONIO_KEY_INPUT_ENTER: /* In paste mode, an HTML newline, otherwise sends the current IM or executes the current command */
                        {
                              int   temppaste = inpaste;

                              if (!temppaste && (buf[0] != 0) && (buf[0] != '/') && (getvar_int(curconn, "autopaste") != 0)) {
                                    fd_set      rfd;
                                    struct timeval    timeout;

                                    FD_ZERO(&rfd);
                                    FD_SET(STDIN_FILENO, &rfd);
                                    timeout.tv_sec = 0;
                                    timeout.tv_usec = 1;
                                    select(STDIN_FILENO+1, &rfd, NULL, NULL, &timeout);
                                    if (FD_ISSET(STDIN_FILENO, &rfd))
                                          temppaste = 1;
                              }
                              if (temppaste) {
                                    ADDTOBUF('<');
                                    ADDTOBUF('b');
                                    ADDTOBUF('r');
                                    ADDTOBUF('>');
                                    inwhite = 1;
                                    break;
                              }
                        }
                        if (buf[0] == 0)
                              break;

                        histpos = histc-1;
                        STRREPLACE(histar[histpos], buf);
                        histpos = histc++;
                        histar = realloc(histar, histc*sizeof(*histar));
                        histar[histpos] = NULL;
                        conio_handleline(buf);
                  case CONIO_KEY_INPUT_KILL: /* Delete the entire input line */
                        memset(buf, 0, sizeof(buf));
                        inwhite = inpaste =
                              bufloc = 0;
                        break;
                  case CONIO_KEY_INPUT_KILL_WORD: /* Delete the input line from the current character to the beginning of the previous word */
                        if (bufloc > 0) {
                              int   end = bufloc;

                              bufloc--;
                              while ((bufloc > 0) && isspace(buf[bufloc]))
                                    bufloc--;
                              while ((bufloc > 0) && !isspace(buf[bufloc]))
                                    bufloc--;
                              if (isspace(buf[bufloc]))
                                    bufloc++;
                              //memmove(buf+bufloc, buf+end, strlen(buf+end)+1);
                              memmove(buf+bufloc, buf+end, strlen(buf+end));
                              memset(buf+strlen(buf)-(end-bufloc), 0, end-bufloc);
                        }
                        break;
                  case CONIO_KEY_INPUT_KILL_EOL: /* Delete the input line from the current character to the end of the line */
                        memset(buf+bufloc, 0, strlen(buf+bufloc));
                        break;
                  case CONIO_KEY_INPUT_PASTE: /* Alter the input handler to handle pasted text better */
                        inpaste = (inpaste == 0);
                        break;
                  case CONIO_KEY_INPUT_SYM_LT: /* In paste mode, an HTML less-than, otherwise a literal less-than */
                        if (inpaste)
                              ADDTOBUF('<');
                        else {
                              ADDTOBUF('&');
                              ADDTOBUF('l');
                              ADDTOBUF('t');
                              ADDTOBUF(';');
                        }
                        break;
                  case CONIO_KEY_INPUT_SYM_GT: /* In paste mode, an HTML greater-than, otherwise a literal greater-than */
                        if (inpaste)
                              ADDTOBUF('>');
                        else {
                              ADDTOBUF('&');
                              ADDTOBUF('g');
                              ADDTOBUF('t');
                              ADDTOBUF(';');
                        }
                        break;
                  case CONIO_KEY_INPUT_SYM_AMP: /* In paste mode, an HTML ampersand, otherwise a literal ampersand */
                        if (inpaste)
                              ADDTOBUF('&');
                        else {
                              ADDTOBUF('&');
                              ADDTOBUF('a');
                              ADDTOBUF('m');
                              ADDTOBUF('p');
                              ADDTOBUF(';');
                        }
                        break;
                  case CONIO_KEY_INPUT_ENT_BOLD: /* Toggle HTML bold mode */
                        if (strncasecmp(buf+bufloc, "</B>", 4) == 0)
                              bufloc += 4;
                        else if (bufloc+sizeof("<I></I>")-1 < sizeof(buf)-1) {
                              ADDTOBUF('<');
                              ADDTOBUF('B');
                              ADDTOBUF('>');
                              ADDTOBUF('<');
                              ADDTOBUF('/');
                              ADDTOBUF('B');
                              ADDTOBUF('>');
                              bufloc -= 4;
                        } else
                              beep();
                        break;
                  case CONIO_KEY_INPUT_ENT_ITALIC: /* Toggle HTML italic (inverse) mode */
                        if (strncasecmp(buf+bufloc, "</I>", 4) == 0)
                              bufloc += 4;
                        else if (bufloc+sizeof("<I></I>")-1 < sizeof(buf)-1) {
                              ADDTOBUF('<');
                              ADDTOBUF('I');
                              ADDTOBUF('>');
                              ADDTOBUF('<');
                              ADDTOBUF('/');
                              ADDTOBUF('I');
                              ADDTOBUF('>');
                              bufloc -= 4;
                        } else
                              beep();
                        break;
                  case CONIO_KEY_WINLIST_HIDE: /* Cycle between always visible, always hidden, or auto-hidden */
                        if (changetime == 0)
                              changetime = nowf;
                        else if (changetime == -1)
                              changetime = 0;
                        else
                              changetime = -1;
                        break;
                  case CONIO_KEY_STATUS_DISPLAY: /* Display or hide the status console */
                        if (consolescroll == -1) {
                              if (secs_getvar_int("quakestyle") == 1) {
                                    quakeoff = 2;
                                    nw_mvwin(&win_info, faimconf.wstatus.starty+2*faimconf.wstatus.widthy/3+1,
                                          faimconf.winfo.startx);
                                    nw_mvwin(&win_input, faimconf.wstatus.starty+2*faimconf.wstatus.widthy/3,
                                          faimconf.winput.startx);
                              }
                              consolescroll = 0;
                        } else {
                              if (secs_getvar_int("quakestyle") == 1) {
                                    nw_mvwin(&win_info, faimconf.winfo.starty,
                                          faimconf.winfo.startx);
                                    nw_mvwin(&win_input, faimconf.winput.starty,
                                          faimconf.winput.startx);
                              }
                              quakeoff = 0;
                              consolescroll = -1;
                        }
                        bupdate();
                        break;
                  case CONIO_KEY_STATUS_POKE: /* Bring the status console down for $autohide seconds */
                        naim_lastupdate(curconn);
                        bupdate();
                        break;
                  case CONIO_KEY_BUDDY_SCROLL_BACK: /* Scroll the current window backwards (up) */
                        if (consolescroll == -1) {
                              scrollbackoff += faimconf.wstatus.widthy-2;
                              if (scrollbackoff >= faimconf.wstatus.pady-faimconf.wstatus.widthy)
                                    scrollbackoff = faimconf.wstatus.pady-faimconf.wstatus.widthy-1;
                        } else {
                              consolescroll += 2*faimconf.wstatus.widthy/3-2;
                              if (consolescroll >= faimconf.wstatus.pady-2*faimconf.wstatus.widthy/3)
                                    consolescroll = faimconf.wstatus.pady-2*faimconf.wstatus.widthy/3-1;
                        }
                        break;
                  case CONIO_KEY_BUDDY_SCROLL_FORWARD: /* Scroll the current window forwards in time */
                        if (consolescroll == -1) {
                              scrollbackoff -= faimconf.wstatus.widthy-2;
                              if (scrollbackoff < 0)
                                    scrollbackoff = 0;
                        } else {
                              consolescroll -= 2*faimconf.wstatus.widthy/3-2;
                              if (consolescroll < 0)
                                    consolescroll = 0;
                        }
                        break;
                  case CONIO_KEY_INPUT_CURSOR_HOME: /* Jump to the beginning of the input line */
                        bufloc = 0;
                        break;
                  case CONIO_KEY_INPUT_CURSOR_END: /* Jump to the end of the input line */
                        bufloc = strlen(buf);
                        break;
                  case CONIO_KEY_INPUT_CURSOR_HOME_END: /* Jump between the beginning and end of the input line */
                        if (bufloc == 0)
                              bufloc = strlen(buf);
                        else
                              bufloc = 0;
                        break;
                }
            } else if (binding != NULL)
                  secs_script_parse(binding);
            if (bindfunc != NULL)
                  bindfunc(buf, &bufloc);
      } else if (naimisprint(c)) {
            ADDTOBUF(c);
            inwhite = 0;
      } else {
            char  numbuf[5];
            int   i;

            snprintf(numbuf, sizeof(numbuf), "%i", c);
            ADDTOBUF('&');
            ADDTOBUF('#');
            for (i = 0; numbuf[i] != 0; i++)
                  ADDTOBUF(numbuf[i]);
            ADDTOBUF(';');
      }

      withtextcomp = 0;
      nw_erase(&win_input);
      {
            int   off = (bufloc < faimconf.winput.widthx)?0:
                        faimconf.winput.widthx+(faimconf.winput.widthx-10)*((bufloc-faimconf.winput.widthx)/(faimconf.winput.widthx-10))-10;

            if (buf[bufloc] == 0) {
                  int   match;
                  const char
                        *str,
                        *desc;

                  nw_printf(&win_input, C(INPUT,TEXT), 1, "%s", buf+off);
                  if ((bufloc > 1) && (buf[0] == '/') && (buf[bufloc-1] != ' ')
                        && ((str = conio_tabcomplete(buf, bufloc, &match, &desc)) != NULL)
                        && (str[match] != 0)) {
                        nw_printf(&win_input, CI(INPUT,EVENT), 0, "%c", 
                              str[match]);
                        nw_printf(&win_input, C(INPUT,EVENT), 0, "%s", 
                              str+match+1);
                        if (desc != NULL)
                              nw_printf(&win_input, C(INPUT,TEXT), 0, " %s", desc);
                        withtextcomp = 1;
                  } else if ((bufloc > 5) && (buf[0] != '/')) {
                        static int  tmp = -1;

                        if ((tmp == -1) || (strncasecmp(histar[tmp], buf, bufloc) != 0))
                              for (tmp = histpos-1; tmp >= 0; tmp--)
                                    if (strncasecmp(histar[tmp], buf, bufloc) == 0)
                                          break;

                        if ((tmp >= 0) && (histar[tmp][bufloc] != 0)) {
                              withtextcomp = 2;
                              nw_printf(&win_input, CI(INPUT,EVENT), 0, "%c", 
                                    histar[tmp][bufloc]);
                              nw_printf(&win_input, C(INPUT,EVENT), 0, "%s", 
                                    histar[tmp]+bufloc+1);
                        } else
                              nw_printf(&win_input, CI(INPUT,TEXT), 0, " ");
                  } else
                        nw_printf(&win_input, CI(INPUT,TEXT), 0, " ");
            } else {
                  int   len = bufloc-off;

                  nw_printf(&win_input, C(INPUT,TEXT), 1, "%.*s", len,
                        buf+off);
                  nw_printf(&win_input, CI(INPUT,TEXT), 0, "%c",
                        buf[bufloc]);
                  if (len < faimconf.winput.widthx-1)
                        nw_printf(&win_input, C(INPUT,TEXT), 1, "%s", 
                              buf+off+len+1);
            }
      }
      nw_touchwin(&win_input);
      nw_touchwin(&win_info);
}

void  gotkey(int c) {
      fd_set      rfd;
      struct timeval    timeout = { 0, 0 };

      gotkey_real(c);

      FD_SET(STDIN_FILENO, &rfd);
      while (select(STDIN_FILENO+1, &rfd, NULL, NULL, &timeout) > 0)
            gotkey_real(nw_getch());
}

Generated by  Doxygen 1.6.0   Back to index