/*
 * $Id: os2 13636 2004-05-05 22:01:21Z hubert $
 *
 * Program:	Operating system dependent routines - OS/2 Text mode
 *
 *
 * Michael Seibel
 * Networks and Distributed Computing
 * Computing and Communications
 * University of Washington
 * Administration Builiding, AG-44
 * Seattle, Washington, 98195, USA
 * Internet: mikes@cac.washington.edu
 *
 * Please address all bugs and comments to "pine-bugs@cac.washington.edu"
 *
 *
 * Pine and Pico are registered trademarks of the University of Washington.
 * No commercial use of these trademarks may be made without prior written
 * permission of the University of Washington.
 * 
 * Pine, Pico, and Pilot software and its included text are Copyright
 * 1989-2001 by the University of Washington.
 * 
 * The full text of our legal notices is contained in the file called
 * CPYRIGHT, included with this distribution.
 *
 *
 * Notes:
 *      - mouse support added (mss, 921215)
 *
 *  Portions of this code derived from MicroEMACS 3.10:
 *
 *	OS2.C:	Operating specific I/O and Spawning functions
 *			under the OS/2 operating system
 *			for MicroEMACS 3.10
 *			(C)opyright 1988 by Daniel M. Lawrence
 *
 */


#include	<process.h>


/*
 * Internal functions...
 */
int   checkmouse PROTO((unsigned *, int, int, int));
void  invert_label PROTO((int, MENUITEM *));
int   enhanced_keybrd PROTO((void));
int   dont_interrupt PROTO((void));
int   interrupt_ok PROTO((void));
int   kbseq PROTO((int *));
int   specialkey PROTO((unsigned int));
void  do_alarm_signal PROTO((void));
void  do_hup_signal PROTO((int sig));
char *pfnexpand PROTO((char *, size_t));
int   ssleep PROTO((long));
void  mouseon PROTO((void));
void  mouseoff PROTO((void));
void  turnmouseoff PROTO((void));

/*
 * Useful global def's
 */

int timeo = 0;
time_t time_of_last_input;
int (*pcollator)();
static	int oldbut;			/* Previous state of mouse buttons */
static  int enhncd;			/* Enhanced keyboard */
static  KBDINFO initialKbdInfo;

#ifdef	MOUSE
/*
 * Useful definitions...
 */
static int mexist = 0;		/* is the mouse driver installed? */
static int nbuttons;		/* number of buttons on the mouse */
static unsigned mnoop;
static unsigned char okinfname[32] = {
      0,    0, 			/* ^@ - ^G, ^H - ^O  */
      0,    0,			/* ^P - ^W, ^X - ^_  */
      0,    0x17,		/* SP - ' ,  ( - /   */
      0xff, 0xe4,		/*  0 - 7 ,  8 - ?   */
      0x7f, 0xff,		/*  @ - G ,  H - O   */
      0xff, 0xe9,		/*  P - W ,  X - _   */
      0x7f, 0xff,		/*  ` - g ,  h - o   */
      0xff, 0xf6,		/*  p - w ,  x - DEL */
      0,    0, 			/*  > DEL   */
      0,    0,			/*  > DEL   */
      0,    0, 			/*  > DEL   */
      0,    0, 			/*  > DEL   */
      0,    0 			/*  > DEL   */
};
#endif


static void set_kbd(int state)
{
  KBDINFO kbdInfo = initialKbdInfo;
  if (state)
  {
    kbdInfo.fsMask &= ~(0x0001|0x0008|0x0100); /* echo,cooked */
    kbdInfo.fsMask |= (0x0002|0x0004|0x0100);  /* noecho,raw,shift-rpt on */
  }
  KbdSetStatus(&kbdInfo, 0);
}

/*
 * DISable ctrl-break interruption
 */
dont_interrupt()
{
  signal (SIGINT, SIG_IGN);
  signal (SIGBREAK, SIG_IGN);
  set_kbd(1);
}

/*
 * re-enable ctrl-break interruption
 */
interrupt_ok()
{
  set_kbd(0);
  signal (SIGINT, SIG_DFL);
  signal (SIGBREAK, SIG_DFL);
}


/*
 * return true if an enhanced keyboard is present
 */
enhanced_keybrd()
{
  return(1);
}


/*
 * This function is called once to set up the terminal device streams.
 */
ttopen()
{
  initialKbdInfo.cb = sizeof(initialKbdInfo);
  KbdGetStatus(&initialKbdInfo, 0);
  dont_interrupt();			/* don't allow interrupt */
  enhncd = enhanced_keybrd();		/* check for extra keys */
#if	MOUSE
  init_mouse();
#else	/* !MOUSE */
  mexist = 0;
#endif	/* MOUSE */
  return(1);
}

#ifdef	MOUSE
HMOU Mouse_Handle =(HMOU)-1;
#ifdef FASTVIO
int mouse_state =0;
#endif

/* 
 * init_mouse - check for and initialize mouse driver...
 */
init_mouse()
{
  if (Mouse_Handle==(HMOU)-1) {
    int rc = MouOpen(NULL, &Mouse_Handle);
    mexist=0;
    if (rc)
      return(FALSE);
    mexist=1;
    nbuttons=2;
    MouFlushQue(Mouse_Handle);
    turnmouseoff();
  }
  return(TRUE);
}

static int mouchk()
{
  if (mexist)
    return (Mouse_Handle==(HMOU)-1) ? init_mouse() : TRUE;
  return FALSE;
}

deinit_mouse()
{
  turnmouseoff();
}


/*
 * mouseon - call made available for programs calling pico to turn ON the
 *           mouse cursor.
 */
void
mouseon()
{
  /* Show Cursor */
  if (mouchk() && !mouse_state) {
#ifdef FASTVIO
    mouse_state=TRUE;
    vidUpdate();
#else
    MouDrawPtr(Mouse_Handle);
#endif
  }
}

void
turnmouseoff()
{
  static NOPTRRECT r = { 0, 0, (USHORT)-1, (USHORT)-1 };
  if (r.cRow == (USHORT)-1) {
    VIOMODEINFO mi;
    mi.cb = sizeof mi;
    VioGetMode(&mi, 0);
    r.cRow = mi.row-1;
    r.cCol = mi.col-1;
  }
  /* Hide Cursor */
  MouRemovePtr(&r, Mouse_Handle);
  mouse_state=FALSE;
}

/*
 * mouseon - call made available for programs calling pico to turn OFF the
 *           mouse cursor.
 */
void
mouseoff()
{
#ifdef FASTVIO
  /* This is ignored if in FASTVIO mode */
#else
  if (mouchk()) {
    turnmouseoff();
  }
#endif
}
#endif


/*
 * This function gets called just before we go back home to the command
 * interpreter.
 */
ttclose()
{
  if(!Pmaster)
    interrupt_ok();
#ifdef MOUSE
  deinit_mouse();
#endif
  return(1);
}


/*
 * Flush terminal buffer. Does real work where the terminal output is buffered
 * up. A no-operation on systems where byte at a time terminal I/O is done.
 */
ttflush()
{
  return(1);
}


/*
 * specialkey - return special key definition
 */
specialkey(kc)
unsigned  kc;
{
  switch(kc){
  case 0x3b00 : return(F1);
  case 0x3c00 : return(F2);
  case 0x3d00 : return(F3);
  case 0x3e00 : return(F4);
  case 0x3f00 : return(F5);
  case 0x4000 : return(F6);
  case 0x4100 : return(F7);
  case 0x4200 : return(F8);
  case 0x4300 : return(F9);
  case 0x4400 : return(F10);
  case 0x8500 : return(F11);
  case 0x8600 : return(F12);
  case 0x4800 : return(KEY_UP);
  case 0x5000 : return(KEY_DOWN);
  case 0x4b00 : return(KEY_LEFT);
  case 0x4d00 : return(KEY_RIGHT);
  case 0x4700 : return(KEY_HOME);
  case 0x4f00 : return(KEY_END);
  case 0x4900 : return(KEY_PGUP);
  case 0x5100 : return(KEY_PGDN);
  case 0x5300 : return(KEY_DEL);
  case 0x48e0 : return(KEY_UP);			/* grey key version */
  case 0x50e0 : return(KEY_DOWN);		/* grey key version */
  case 0x4be0 : return(KEY_LEFT);		/* grey key version */
  case 0x4de0 : return(KEY_RIGHT);		/* grey key version */
  case 0x47e0 : return(KEY_HOME);		/* grey key version */
  case 0x4fe0 : return(KEY_END);		/* grey key version */
  case 0x49e0 : return(KEY_PGUP);		/* grey key version */
  case 0x51e0 : return(KEY_PGDN);		/* grey key version */
  case 0x53e0 : return(KEY_DEL);		/* grey key version */
  default     : return(NODATA);
  }
}


/*
 * Read a character from the terminal, performing no editing and doing no echo
 * at all. Also mouse events are forced into the input stream here.
 */
ttgetc(return_on_intr, recorder, bail_handler)
    int return_on_intr;
    int (*recorder)();
    int (*bail_handler)();
{
    int key = kbd_getkey();
    return key ? key : NODATA;
}


/*
 * ctrlkey - used to check if the key hit was a control key.
 */
ctrlkey()
{
  return !!(kbd_shift() & KBDSTF_CONTROL);
}


/*
 * win_multiplex - give OS/2 a shot at the CPU
 */
win_multiplex()
{
  DosSleep(32);
}


/*
 * Read in a key.
 * Do the standard keyboard preprocessing. Convert the keys to the internal
 * character set.  Resolves escape sequences and returns no-op if global
 * timeout value exceeded.
 */
GetKey()
{
  unsigned ch = 0, lch, intrupt = 0;
  long timein;

  vidUpdate();
  if(mexist || timeo) {
    timein = time(0L);
#ifdef	MOUSE
    mouseon();		/* Show Cursor */
#endif
    while(!kbd_ready()) {
#if	MOUSE
      if(timeo && time(0L) >= timein+timeo){
	mouseoff();		/* Hide Cursor */
	return(NODATA);
      }

      if(checkmouse(&ch,0,0,0)){		/* something happen ?? */
	mouseoff();		/* Hide Cursor */
	curwp->w_flag |= WFHARD;
	return(ch);
      }
#else
      if(time(0L) >= timein+timeo) {
	mouseoff();		/* Hide Cursor */
	return(NODATA);
      }
#endif	/* MOUSE */

      /*
       * Surrender the CPU...
       */
      win_multiplex();
    }
#ifdef	MOUSE
    mouseoff();		/* Hide Cursor */
#endif	/* MOUSE */
  }

  ch = (*term.t_getchar)(0, NULL, NULL);
  lch = (ch&0xff);

  if(lch & 0x80 && Pmaster && Pmaster->hibit_entered)
    *Pmaster->hibit_entered = 1;

  return((lch && (lch != 0xe0 || !(ch & 0xff00))) 
		 ? (lch < ' ') ? (CTRL|(lch + '@')) 
		 : (lch == ' ' && ctrlkey()) ? (CTRL|'@') : lch
		 : specialkey(ch));
}


#if	MOUSE
/* 
 * checkmouse - look for mouse events in key menu and return 
 *              appropriate value.
 */
int
checkmouse(ch, down, xxx, yyy)
unsigned *ch;
int down, xxx, yyy;
{
  MOUQUEINFO qi;

  if(!mouchk())
    return(FALSE);

  if (MouGetNumQueEl(&qi, Mouse_Handle)==0 && qi.cEvents)
  {
    MOUEVENTINFO m;
    USHORT w = MOU_NOWAIT;

    /* check to see if any mouse buttons are different */
    if (MouReadEventQue(&m, &w, Mouse_Handle)==0)
    {
      int k;
      int rv = 0;
      int button = M_BUTTON_LEFT;
      int newbut = ((m.fs & 4)?1:0) | ((m.fs & 16)?2:0) | ((m.fs & 64)?4:0);

      /* only notice button changes */
      if (oldbut == newbut)
	return(FALSE);

      for (k=1; k != (1 << nbuttons); k <<= 1) {
	/* For each button on the mouse */
	if ((oldbut&k) != (newbut&k)) {
	  if(k == 1){
	    static int oindex;
	    int i = 0;
	    MENUITEM *mp;

	    if(newbut&k)			/* button down */
	      oindex = -1;

	    for(mp = mfunc; mp; mp = mp->next)
	      if(mp->action && M_ACTIVE(m.row, m.col, mp))
		break;

	    if(mp){
	      unsigned long r;
	      
	      r = (*mp->action)((newbut&k) ? M_EVENT_DOWN : M_EVENT_UP,
				m.row, m.col, button, 0);
	      if(r & 0xffff){
		*ch = (unsigned)((r>>16)&0xffff);
		rv  = TRUE;
	      }
	    }
	    else{
	      while(1){	/* see if we understand event */
		if(i >= 12){
		  i = -1;
		  break;
		}
		
		if(M_ACTIVE(m.row, m.col, &menuitems[i]))
		  break;
		
		i++;
	      }
	      
	      if(newbut&k){			/* button down */
		oindex = i;			/* remember where */
		if(i != -1) {			/* invert label */
		  invert_label(1, &menuitems[i]);
		}
	      }
	      else{				/* button up */
		if(oindex != -1){
		  if(i == oindex){
		    *ch = menuitems[i].val;
		    rv = 1;
		  }
		}
	      }
	    }
	    
	    if(!(newbut&k) && oindex != -1) {
	      invert_label(0, &menuitems[oindex]);	/* restore label */
	    }
	  }
	  
	  oldbut = newbut;
	  return(rv);
	}
	++button;
      }
    }
  }
  return(FALSE);
}


/*
 * invert_label - highlight the label of the given menu item.
 */
void
invert_label(state, m)
int state;
MENUITEM *m;
{
  USHORT r, c;
  VIOCURSORINFO oldInfo, newInfo;
  int i, j, p;
  char *lp;
  int old_state = getrevstate();

  if(m->val == mnoop)
	return;

  VioGetCurPos(&r, &c, 0);
  VioGetCurType(&oldInfo, 0);
  newInfo = oldInfo;
  newInfo.attr = (USHORT)-1;
  VioSetCurType(&newInfo, 0);             /* Hide Cursor */
  (*term.t_move)(m->tl.r, m->tl.c);
  (*term.t_rev)(state);
  for(i = m->tl.r; i <= m->br.r; i++)
    for(j = m->tl.c; j <= m->br.c; j++)
      if(i == m->lbl.r && j == m->lbl.c){	/* show label?? */
	lp = m->label;
	while(*lp && j++ < m->br.c)
	  (*term.t_putchar)(*lp++);
	continue;
      }
      else (*term.t_putchar)(' ');
  
  (*term.t_rev)(old_state);
  VioSetCurPos(r, c, 0);	/* restore old position */
  VioSetCurType(&oldInfo, 0);	/* Show Cursor */
  vidUpdate();
}
#endif	/* MOUSE */


/*
 * alt_editor - fork off an alternate editor for mail message composition
 */
#define MAXARGS 10
alt_editor(f, n)
    int f, n;
{
  char   *fn;					/* tmp holder for file name */
  char   *cp;
  int    child, rc, i, done = 0;
  long   l;
  int    stat;
  FILE   *p;

  char *args[MAXARGS];			/* ptrs into edit command */
  char eb[NLINE];

  if(Pmaster == NULL)
    return(-1);

  if(gmode&MDSCUR){
    emlwrite("Alternate editor not available in restricted mode", NULL);
    return(-1);
  }

  if(Pmaster->alt_ed == NULL){
    if (!(gmode&MDADVN)) {
      emlwrite("\007Unknown Command",NULL);
      return(-1);
    }

    if((cp=getenv("VISUAL"))!=0 || (cp=getenv("EDITOR"))!=0)
      strcpy(eb, (char *)getenv("EDITOR"));
    else *eb = '\0';
    
    while(!done) {
      rc = mlreplyd("Which alternate editor ? ",eb,NLINE,QDEFLT,NULL);

      switch(rc) {
      case ABORT:
	return(-1);
      case HELPCH:
	emlwrite("no alternate editor help yet", NULL);
	
	/* take sleep and break out after there's help */
	sleep(3);
	break;
      case (CTRL|'L'):
	sgarbf = TRUE;
      update();
      break;
      case TRUE:
      case FALSE:			/* does editor exist ? */
	if(*eb == '\0'){		/* leave silently? */
	  mlerase();
	  curwp->w_flag |= WFMODE;
	  return(-1);
	}

	done++;
	break;
      default:
	break;
      }
    }
  }
  else
    strcpy(eb, Pmaster->alt_ed);

  if((fn=writetmp(1, NULL)) == NULL){		/* get temp file */
    emlwrite("Problem writing temp file for alt editor", NULL);
    return(-1);
  }

  strcat(eb, " ");
  strcat(eb, fn);

  cp = eb;
  for(i=0; *cp != '\0';i++) {			/* build args array */
    if(i < MAXARGS) {
      args[i] = NULL;			/* in case we break out */
    }
    else{
      emlwrite("Too many args for command!", NULL);
      return(-1);
    }

    while(isspace((unsigned char)(*cp)))
      if(*cp != '\0')
	cp++;
      else break;

    args[i] = cp;
    while(!isspace((unsigned char)(*cp)))
      if(*cp != '\0')
	cp++;
      else
	break;
    
    if(*cp != '\0')
      *cp++ = '\0';
  }

  args[i] = NULL;

  (*Pmaster->tty_fix)(0);

  emlwrite("Invoking alternate editor...", NULL);

  {
    void (*ohup)() = signal(SIGHUP, SIG_IGN);	/* ignore signals for now */
    void (*oint)() = signal(SIGINT, SIG_IGN);
    cp=args[0];
    rc = spawnvp(P_WAIT, cp, args);
    signal(SIGHUP, ohup);	/* restore signals */
    signal(SIGINT, oint);
  }

  (*Pmaster->tty_fix)(1);

  /*
   * Editor may have set a hibit, we don't know. Assume it did.
   */
  if(!f && Pmaster && Pmaster->hibit_entered)
   *Pmaster->hibit_entered = 1;

  dont_interrupt();

  if (rc==-1) {  /* Can't run it */
	  emlwrite("error attempting to run alt editor", NULL);
  }
  /*
   * replace edited text with new text 
   */
  else{
    rc = 0;
    curbp->b_flag &= ~BFCHG;		/* make sure old text gets blasted */
    readin(fn, 0, 0);			/* read new text overwriting old */
    curbp->b_flag |= BFCHG;		/* mark dirty for packbuf() */
  }
  unlink(fn);				/* blast temp file */

  ttopen();				/* reset the signals */
  pico_refresh(0, 1);			/* redraw */
  return(rc);
}


/*
 *  bktoshell - suspend and wait to be woken up
 */
int
bktoshell()		/* suspend MicroEMACS and wait to wake up */
{
  int i;
  static char * shell = NULL;

  if (shell == NULL) {
    char *p;
    shell=getenv("SHELL");
    if (!shell && !(shell=getenv("COMSPEC")))
      shell="CMD.EXE";
    if ((p = strdup(shell)) > 0) {
      for (shell = p; (p = strchr(shell, '/')) != 0; )
	*p = '\\';
    }
  }

  (*term.t_move)(term.t_nrow, 0);
  (*term.t_eeol)();
  exit_text_mode(NULL);
  interrupt_ok();
  if (system(shell) == -1)
    emlwrite("Error loading %s", shell);
  else pico_refresh(0, 1);			/* redraw */
  enter_text_mode(NULL);
  dont_interrupt();
  return(1);
}


/*
 * P_open - run the given command in a sub-shell returning a file pointer
 *	    from which to read the output
 *
 * note:
 *	For OS's other than unix, you will have to rewrite this function.
 *	Hopefully it'll be easy to exec the command into a temporary file, 
 *	and return a file pointer to that opened file or something.
 */
FILE *P_open(c)
char *c;
{
  return(popen(c,"r"));
}



/*
 * P_close - close the given descriptor
 *
 */
void
P_close(fp)
FILE *fp;
{
  (void)pclose(fp);
}

/*
 * A replacement for fflush
 * relies on #define fflush os2_fflush
 */
#undef fflush
int
os2_fflush (FILE *f)
{
  if (f == stdout) {
    vidUpdate();
  }
  else
    fflush (f);
}

/*
 * ttresize - recompute the screen dimensions if necessary, and then
 *	      adjust pico's internal buffers accordingly
 */
void
ttresize ()
{
    return;
}

/*
 * picosigs - Install any handlers for the signals we're interested
 *	      in catching.
 */
void
picosigs()
{
    signal(SIGHUP,  do_hup_signal);	/* deal with SIGHUP */
    signal(SIGTERM, do_hup_signal);	/* deal with SIGTERM */
}

/*
 * do_hup_signal - jump back in the stack to where we can handle this
 */
void
do_hup_signal(int sig)
{
  sig=sig;
  signal(SIGHUP,  SIG_IGN);			/* ignore further SIGHUP's */
  signal(SIGTERM, SIG_IGN);			/* ignore further SIGTERM's */
  if(Pmaster){
    extern jmp_buf finstate;

    longjmp(finstate, COMP_GOTHUP);
  }
  else{
    /*
     * if we've been interrupted and the buffer is changed,
     * save it...
     */
    if(anycb() == TRUE){			/* time to save */
      if(curbp->b_fname[0] == '\0'){	/* name it */
	strcpy(curbp->b_fname, "pico.sav");
      }
      else{
	strcat(curbp->b_fname, ".sav");
      }
      writeout(curbp->b_fname, TRUE);
    }
    vttidy();
    exit(1);
  }
}



#ifdef	MOUSE

/*
 * end_mouse - a no-op on DOS/Windows
 */
void
end_mouse()
{
}


/*
 * mouseexist - function to let outsiders know if mouse is turned on
 *              or not.
 */
mouseexist()
{
    return(mexist);
}
#endif	/* MOUSE */


/*
 * fallowc - returns TRUE if c is allowable in filenames, FALSE otw
 */
fallowc(c)
int c;
{
  return(okinfname[c>>3] & 0x80>>(c&7));
}


/*
 * fexist - returns TRUE if the file exists, FALSE otherwise
 */
fexist(file, m, l)
char  *file, *m;
off_t *l;
{
  struct stat	sbuf;

  if(l != NULL)
	*l = (off_t)0;

  if(stat(file, &sbuf) < 0){
	if(ENOENT)				/* File not found */
	  return(FIOFNF);
	else
	  return(FIOERR);
  }

  if(l != NULL)
	*l = (off_t)sbuf.st_size;

  if(sbuf.st_mode & S_IFDIR)
	return(FIODIR);

  if(m[0] == 'r')				/* read access? */
	return((S_IREAD & sbuf.st_mode) ? FIOSUC : FIONRD);
  else if(m[0] == 'w')			/* write access? */
	return((S_IWRITE & sbuf.st_mode) ? FIOSUC : FIONWT);
  else if(m[0] == 'x')			/* execute access? */
	return((S_IEXEC & sbuf.st_mode) ? FIOSUC : FIONEX);
  return(FIOERR);				/* what? */
}


/*
 * isdir - returns true if fn is a readable directory, false otherwise
 *         silent on errors (we'll let someone else notice the problem;)).
 */
isdir(fn, l, d)
char *fn;
long *l;
time_t *d;
{
  struct stat sbuf;

  if(l)
	*l = 0;

  if(stat(fn, &sbuf) < 0)
	return(0);

  if(l)
    *l = sbuf.st_size;

  if(d)
    *d = sbuf.st_mtime;

  return(sbuf.st_mode & S_IFDIR);
}


/*
 * gethomedir - returns the users home directory
 *              Note: home is malloc'd for life of pico
 */
char *gethomedir(l)
int *l;
{
  static char *home = NULL;
  static short hlen = 0;

  if(home == NULL){
	char *p, buf[NLINE];

	if (home=getenv("PINEHOME"))	   /* Overrides all others */
	  strcpy(buf, home);
	else if (home=getenv("HOME"))	   /* Convenient group placement */
	  strcpy(buf, home);
	else if (home=getenv("ETC"))	   /* IBM TCPIP */
	  strcpy(buf, home);
	else sprintf(buf, "%c:\\", _getdrive());
	hlen = strlen(buf);
	if ((home=(char *)malloc(hlen + 1)) == NULL) {
	  emlwrite("Problem allocating space for home dir", NULL);
	  return(0);
	}
	strcpy(home, buf);
	while ((p=strchr(home,'/')) != NULL)   /* Normalise, just in case */
	  *p = '\\';
  }

  if(l)
	*l = hlen;

  return(home);
}


/*
 * homeless - returns true if given file does not reside in the current
 *            user's home directory tree. 
 */
homeless(f)
char *f;
{
  char *home;
  int   len;

  home = gethomedir(&len);
  return(strncmp(home, f, len));
}



/*
 * errstr - return system error string corresponding to given errno
 *          Note: strerror() is not provided on all systems, so it's 
 *          done here once and for all.
 */
char *errstr(err)
int err;
{
  return((err >= 0 && err < sys_nerr) ? (char*)sys_errlist[err] : NULL);
}


/*
 * getfnames - return all file names in the given directory in a single 
 *             malloc'd string.  n contains the number of names
 */
char *getfnames(dn, pat, n, e)
char *dn, *pat, *e;
int  *n;
{
    int status;
    long l;
    size_t avail, alloced, incr = 1024;
    char *names, *np, *p;
    char buf[NLINE];
    struct stat sbuf;
    ULONG count=1;
    FILEFINDBUF3 findbuf;
    HDIR hdir=HDIR_CREATE;

    *n = 0;

    while ((p = strchr(dn, '/')) != NULL)
      *p = '\\';

    if(stat(dn, &sbuf) < 0){
	if(e)
	  sprintf(e, "\007Dir \"%s\": %s", dn, strerror(errno));

	return(NULL);
    } 
    else{
#define MAX(x,y)        ((x) > (y) ? (x) : (y))
	avail = alloced = MAX(sbuf.st_size, incr);
	if(!(sbuf.st_mode & S_IFDIR)){
	    if(e)
	      sprintf(e, "\007Not a directory: \"%s\"", dn);

	    return(NULL);
	}
    }

    if((names=(char *)malloc(alloced * sizeof(char))) == NULL){
	if(e)
	  sprintf(e, "\007Can't malloc space for file names");

	return(NULL);
    }

    np = names;

    strcpy(buf, dn);
    if (*buf && buf[strlen(buf)-1] != '\\')
      strcat(buf, "\\");
    if (pat && *pat)
      strcat(buf, pat);
    if (!pat || !*pat || strchr(pat, '.')==NULL)
      strcat(buf, "*");

    if (DosFindFirst(buf, &hdir, FILE_NORMAL|FILE_DIRECTORY, &findbuf,
		     sizeof findbuf, &count, FIL_STANDARD) != 0) {
	if(e)
	  sprintf(e, "Can't find first file in \"%s\"", dn);

	free((char *) names);
	return(NULL);
    }

    do{
	(*n)++;
	p = findbuf.achName;
	l = strlen(p);
	while(avail < l+1){
	    char *oldnames;

	    alloced += incr;
	    avail += incr;
	    oldnames = names;
	    if((names = (char *)realloc((void *)names, alloced * sizeof(char)))
		== NULL){
		if(e)
		  sprintf(e, "\007Can't malloc enough space for file names");

		return(NULL);
	    }

	    np = names + (np-oldnames);
	}

	avail -= (l+1);

	while((*np++ = *p++) != '\0')
	  ;
    }
    while(DosFindNext(hdir, &findbuf, sizeof findbuf, &count) == 0);

    return(names);
}


/*
 * fioperr - given the error number and file name, display error
 */
void
fioperr(e, f)
int  e;
char *f;
{
  switch(e){
  case FIOFNF:				/* File not found */
	emlwrite("\007File \"%s\" not found", f);
	break;
  case FIOEOF:				/* end of file */
	emlwrite("\007End of file \"%s\" reached", f);
	break;
  case FIOLNG:				/* name too long */
	emlwrite("\007File name \"%s\" too long", f);
	break;
  case FIODIR:				/* file is a directory */
	emlwrite("\007File \"%s\" is a directory", f);
	break;
  case FIONWT:
	emlwrite("\007Write permission denied: %s", f);
	break;
  case FIONRD:
	emlwrite("\007Read permission denied: %s", f);
	break;
  case FIONEX:
	emlwrite("\007Execute permission denied: %s", f);
	break;
  default:
	emlwrite("\007File I/O error: %s", f);
  }
}


/*
 * pfnexpand - pico's function to expand the given file name if there is 
 *	       a leading '~'
 */
char *pfnexpand(fn, len)
    char  *fn;
    size_t len;
{
  register char *x, *y, *z;
  char *home = gethomedir(NULL);
  char name[20];

  if(*fn == '~') {
	for(x = fn+1, y = name;
	    *x != '/' && *x != '\\' && *x != '\0' && y-name < sizeof(name)-1;
	    *y++ = *x++);
	*y = '\0';
	if(strlen(home) + strlen(fn) >= len) {
	  return(NULL);
	}
	/* make room for expanded path */
	for(z=x+strlen(x),y=fn+strlen(x)+strlen(home);
	    z >= x;
	    *y-- = *z--);
	/* and insert the expanded address */
	for(x=fn,y=home; *y != '\0'; *x++ = *y++);
  }

  return(fn);
}

getcurdir(drv, buf)
int drv;
char *buf;
{
  LONG ml = _MAX_PATH;
  drv = drv ? toupper(drv) : _getdrive();
  buf[0] = (char)drv;
  buf[1] = ':';
  buf[2] = '\\';
  return DosQueryCurrentDir(drv-'A'+1, buf + 3, &ml) == 0;
}

/*
 * fixpath - make the given pathname into an absolute path
 */
void
fixpath(name, len)
    char  *name;
    size_t len;
{
  char *p;
  char file[_MAX_PATH];
  int  dr;

  if(!len)
	return;

  /* normalize: xlate any '/' into '\\' */
  while ((p=strchr(name,'/'))!=NULL)
	*p='\\';

  /* return the full path of given file */
  if(isalpha((unsigned char)name[0]) && name[1] == ':'){ /* have drive spec? */
	if(name[2] == '\\')				 /* including path? */
	  return;
	if (!getcurdir(name[0], file))
	  return;
	name += 2;
  }
  else if(name[0] == '.' && (!name[1] || name[1] == '\\')) {
	getcurdir(0, file);
	name += (1 + name[1] == '\\');
  }
  else if(name[0] == '\\') {		/* no drive spec! */
	file[0] = (char)_getdrive();
	file[1] = ':';
	file[2] = '\0';
  }
  else getcurdir(0, file);	/* no qualification */
  
  if(*name) {				/* if name, append it */
	p = NULL;
	if (name[0] == '.' && name[1] == '.' && (!name[2] || name[2] == '\\')) {
	  if ((p = strrchr(name,'\\'))!=NULL) {
		*p++ = '\0';
		if (!*p && (p=strrchr(name,'\\'))!=NULL)
		  *p = '\0';
	  }
	  name += (2 & name[2] == '\\');
	}
	if (*name) {
	  if (name[0] == '.' && (!name[1] || name[1] == '\\'))
		name += (1 + name[1] == '\\');
	  if (*name) {
		if (*file && file[strlen(file)-1] != '\\' && *name != '\\')
		  strcat(file, "\\");
		strcat(file, name);
	  }
	}
  }

  strncpy(name, file, len-1);			/* copy back to real buffer */
  name[len-1] = '\0';				/* tie off just in case */
}


/*
 * compresspath - given a base path and an additional directory, collapse
 *                ".." and "." elements and return absolute path (appending
 *                base if necessary).  
 *
 *                returns  1 if OK, 
 *                         0 if there's a problem
 *                         new path, by side effect, if things went OK
 */
compresspath(base, path, len)
char *base, *path;
int  len;
{
  register int i;
  int  depth = 0;
  char *p;
  char *stack[32];
  char  pathbuf[NLINE];

#define PUSHD(X)  (stack[depth++] = X)
#define POPD()    ((depth > 0) ? stack[--depth] : "")

  strcpy(pathbuf, path);
  fixpath(pathbuf, len);

  p = pathbuf;
  for(i=0; pathbuf[i] != '\0'; i++){		/* pass thru path name */
	if(pathbuf[i] == C_FILESEP){
	  if(p != pathbuf)
		PUSHD(p);				/* push dir entry */
	  p = &pathbuf[i+1];			/* advance p */
	  pathbuf[i] = '\0';			/* cap old p off */
	  continue;
	}

	if(pathbuf[i] == '.'){			/* special cases! */
	  if(pathbuf[i+1] == '.'			/* parent */
		 && (pathbuf[i+2] == C_FILESEP || pathbuf[i+2] == '\0')){
		if(!strcmp(POPD(),""))		/* bad news! */
		  return(0);

		i += 2;
		p = (pathbuf[i] == '\0') ? "" : &pathbuf[i+1];
	  }
	  else if(pathbuf[i+1] == C_FILESEP || pathbuf[i+1] == '\0'){
		i++;
		p = (pathbuf[i] == '\0') ? "" : &pathbuf[i+1];
	  }
	}
  }

  if(*p != '\0')
	PUSHD(p);					/* get last element */

  path[0] = '\0';
  for(i = 0; i < depth; i++){
	strcat(path, S_FILESEP);
	strcat(path, stack[i]);
  }

  return(1);					/* everything's ok */
}


/*
 * tmpname - return a temporary file name in the given buffer
 */
void
tmpname(dir, name)
char *dir;
char *name;
{
  static int counter = 0;
  static char *tmpdir = NULL;
  char * p;

  if(dir && *dir)
    tmpdir = dir;

  if(!tmpdir && (tmpdir=getenv("TEMP"))==NULL && (tmpdir = getenv("TMP"))==NULL)
    tmpdir=".";
  p = strchr(strcpy(name, tmpdir), '/');
  while (p != NULL)
  {
    *p++ = '\\';
    p = strchr(p, '/');
  }
  p = strrchr(name, '\\');
  if (p == 0)
    p = name + strlen(name);
  else if (*++p != 0)
  {
    p = name + strlen(name);
    *p++ = '\\';
  }
  sprintf(p, "pn%d%d", getpid(), ++counter);

  if(dir && *dir)
    tmpdir = NULL;
}


/*
 * Take a file name, and from it
 * fabricate a buffer name. This routine knows
 * about the syntax of file names on the target system.
 * I suppose that this information could be put in
 * a better place than a line of code.
 */
void
makename(bname, fname)
char    bname[];
char    fname[];
{
  register char   *cp1;
  register char   *cp2;

  cp1 = &fname[0];
  while (*cp1 != 0)
	++cp1;

  while (cp1!=&fname[0] && cp1[-1]!='\\')
	--cp1;
  cp2 = &bname[0];
  while (cp2!=&bname[NBUFN-1] && *cp1!=0 && *cp1!=';')
	*cp2++ = *cp1++;
  *cp2 = 0;
}


/*
 * copy - copy contents of file 'a' into a file named 'b'.  Return error
 *        if either isn't accessible or is a directory
 */
copy(a, b)
char *a, *b;
{
  int    n, rv = 0;
  struct stat tsb, fsb;

  if(stat(a, &fsb) < 0){		/* get source file info */
	emlwrite("Can't Copy: %s", errstr(errno));
	return(-1);
  }

  if(!(fsb.st_mode&S_IREAD)){		/* can we read it? */
	emlwrite("\007Read permission denied: %s", a);
	return(-1);
  }

  if((fsb.st_mode&S_IFMT) == S_IFDIR){ /* is it a directory? */
	emlwrite("\007Can't copy: %s is a directory", a);
	return(-1);
  }

  if(stat(b, &tsb) < 0){		/* get dest file's mode */
	switch(errno){
	case ENOENT:
	  break;			/* these are OK */
	default:
	  emlwrite("\007Can't Copy: %s", errstr(errno));
	  return(-1);
	}
  }
  else{
	if(!(tsb.st_mode&S_IWRITE)){	/* can we write it? */
	  emlwrite("\007Write permission denied: %s", b);
	  return(-1);
	}

	if((tsb.st_mode&S_IFMT) == S_IFDIR){	/* is it directory? */
	  emlwrite("\007Can't copy: %s is a directory", b);
	  return(-1);
	}

	if(fsb.st_dev == tsb.st_dev && fsb.st_ino == tsb.st_ino){
	  emlwrite("\007Identical files.  File not copied", NULL);
	  return(-1);
	}
  }

  rv=DosCopy(a, b, DCPY_EXISTING);

  if(rv != 0 < 0){
	emlwrite("Copy Failed: DosCopy() error %d", rv);
	return(-1);
  }
  return(rv);
}


/*
 * Open a file for writing. Return TRUE if all is well, and FALSE on error
 * (cannot create).
 */
ffwopen(fn, readonly)
char    *fn;
int	 readonly;
{
    extern FIOINFO g_pico_fio;

    g_pico_fio.flags = FIOINFO_WRITE;
    if ((g_pico_fio.fp = fopen(gf_pico_fio.name = fn, "w")) == NULL) {
	emlwrite("Cannot open file for writing", NULL);
	return (FIOERR);
    }

    if(readonly)
      chmod(fn, MODE_READONLY);		/* fix access rights */

    return (FIOSUC);
}


/*
 * Close a file. Should look at the status in all systems.
 */
ffclose()
{
    extern FIOINFO g_pico_fio;

    if (fclose(g_pico_fio.fp) != FALSE) {
	emlwrite("Error closing file", NULL);
	return(FIOERR);
    }

    return(FIOSUC);
}


/*
 * ffelbowroom - make sure the destination's got enough room to receive
 *		 what we're about to write...
 */
ffelbowroom(fn, readonly)
char    *fn;
int      readonly;
{
    return(TRUE);
}


/*
 * worthit - generic sort of test to roughly gage usefulness of using 
 *           optimized scrolling.
 *
 * note:
 *	returns the line on the screen, l, that the dot is currently on
 */
worthit(l)
int *l;
{
  int i;			/* l is current line */
  unsigned below;		/* below is avg # of ch/line under . */

  *l = doton(&i, &below);
  below = (i > 0) ? below/(unsigned)i : 0;

  return(below > 3);
}


/*
 * o_insert - optimize screen insert of char c
 */
o_insert(c)
char c;
{
  return(0);
}


/*
 * o_delete - optimized character deletion
 */
o_delete()
{
  return(0);
}


/*
 * pico_new_mail - just checks mtime and atime of mail file and notifies user 
 *	           if it's possible that they have new mail.
 */
pico_new_mail()
{
  return(0);
}



/*
 * time_to_check - checks the current time against the last time called 
 *                 and returns true if the elapsed time is > below.
 *                 Newmail won't necessarily check, but we want to give it
 *                 a chance to check or do a keepalive.
 */
time_to_check()
{
    static time_t lasttime = 0L;

    if(!timeo)
      return(FALSE);

    if(time((time_t *) 0) - lasttime > (Pmaster ? (time_t)(FUDGE-10) : timeo)){
	lasttime = time((time_t *) 0);
	return(TRUE);
    }
    else
      return(FALSE);
}


/*
 * sstrcasecmp - compare two pointers to strings case independently
 */
sstrcasecmp(s1, s2)
QcompType *s1, *s2;
{
  return((*pcollator)(*(char **)s1, *(char **)s2));
}


int
strucmp(o, r)
    char *o, *r;
{
    return(o ? (r ? stricmp(o, r) : 1) : (r ? -1 : 0));
}


int
struncmp(o, r, n)
    char *o, *r;
    int   n;
{
    return(o ? (r ? strnicmp(o, r, n) : 1) : (r ? -1 : 0));
}


/*
 * sleep the given number of microseconds
 */
ssleep(s)
    clock_t s;
{
  s += clock();
  while(s > clock())
	;
}


/*
 * chkptinit -- initialize anything we need to support composer
 *		checkpointing
 */
void
chkptinit(file, n)
    char *file;
    int   n;
{
    if(!file[0]){
	long gmode_save = gmode;

	if(gmode&MDCURDIR)
	  gmode &= ~MDCURDIR;  /* so fixpath will use home dir */

	strcpy(file, "#picoTM0.txt");
	fixpath(file, NLINE);
	gmode = gmode_save;
    }
    else{
	int l = strlen(file);

	if(file[l-1] != '\\'){
	    file[l++] = '\\';
	    file[l]   = '\0';
	}

	strcpy(file + l, "#picoTM0.txt");
    }

    if(fexist(file, "r", (off_t *)NULL) == FIOSUC){ /* does file exist? */
	char copy[NLINE];

	strcpy(copy, "#picoTM1.txt");
	fixpath(copy, NLINE);
	rename(file, copy);  /* save so we don't overwrite it */
    }

    unlink(file);
}


void
set_collation(collation, ctype)
    int collation;
    int ctype;
{
    extern int collator();  /* strcoll isn't declared on all systems */
#ifdef LC_COLLATE
    char *status = NULL;
#endif

    pcollator = strucmp;

#ifdef LC_COLLATE
  if(collation){
    /*
     * This may not have the desired effect, if collator is not
     * defined to be strcoll in os.h and strcmp and friends
     * don't know about locales. If your system does have strcoll
     * but we haven't defined collator to be strcoll in os.h, let us know.
     */
    status = setlocale(LC_COLLATE, "");

    /*
     * If there is an error or if the locale is the "C" locale, then we
     * don't want to use strcoll because in the default "C" locale strcoll
     * uses strcmp ordering and we want strucmp ordering.
     *
     * The test for "C" isn't really correct, since status does not have to
     * be "C" even if we're in the "C" locale. But this works on some systems.
     */
    if(status && !(status[0] == 'C' && status[1] == '\0'))
      pcollator = collator;
  }
#endif
#ifdef LC_CTYPE
  if(ctype){
    (void)setlocale(LC_CTYPE, "");
  }
#endif
}


static USHORT myShift = 0;

int kbd_ready()
{
  KBDKEYINFO ki;

  if (KbdPeek(&ki, 0)==0)
  {
    if (ki.fbStatus & 0x40) {
      int key = (USHORT)((ki.chScan << 8) | ki.chChar);
      myShift = ki.fsState;
      if (!key && ki.fbStatus & 0x01) {
        KbdCharIn(&ki, IO_WAIT, 0);
      }
      return key;
    }
  }
  return 0;
}

int kbd_getkey()
{
  KBDKEYINFO ki;
  
  while (KbdCharIn(&ki, IO_WAIT, 0)==0) {
    if (ki.fbStatus & 0x40) {
      myShift = ki.fsState;
      return (USHORT)((ki.chScan << 8) | ki.chChar);
    }
    if (ki.fbStatus & 0x01) {
      myShift = ki.fsState;
    }
  }
  
  return NODATA;
}

void kbd_flush()
{
  KbdFlushBuffer(0);
}

int kbd_shift()
{
  return myShift;
}


