/*
 * $Id: os_dos.c,v 4.25 1994/10/10 15:59:36 mikes Exp $
 *
 * Program:	Operating system dependent routines - MS DOS
 *
 *
 * 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"
 *
 * Copyright 1991-1994  University of Washington
 *
 *  Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee to the University of
 * Washington is hereby granted, provided that the above copyright notice
 * appears in all copies and that both the above copyright notice and this
 * permission notice appear in supporting documentation, and that the name
 * of the University of Washington not be used in advertising or publicity
 * pertaining to distribution of the software without specific, written
 * prior permission.  This software is made available "as is", and
 * THE UNIVERSITY OF WASHINGTON DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED,
 * WITH REGARD TO THIS SOFTWARE, INCLUDING WITHOUT LIMITATION ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, AND IN
 * NO EVENT SHALL THE UNIVERSITY OF WASHINGTON BE LIABLE FOR ANY SPECIAL,
 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, TORT
 * (INCLUDING NEGLIGENCE) OR STRICT LIABILITY, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Pine and Pico are trademarks of the University of Washington.
 * No commercial use of these trademarks may be made without prior
 * written permission of the University of Washington.
 *
 *
 * Notes:
 *      - mouse support added (mss, 921215)
 *
 *  Portions of this code derived from MicroEMACS 3.10:
 *
 *	MSDOS.C:	Operating specific I/O and Spawning functions
 *			under the MS/PCDOS operating system
 *			for MicroEMACS 3.10
 *			(C)opyright 1988 by Daniel M. Lawrence
 *
 */

#include 	<stdio.h>
#include	<errno.h>
#include	<signal.h>
#include	<setjmp.h>
#include	<time.h>
#include	<fcntl.h>
#include	<io.h>
#include	<bios.h>

#include	"osdep.h"
#include	"estruct.h"
#include        "edef.h"
#include        "pico.h"
#include        "dos_gen.h"


/*
 * Internal functions...
 */
int   checkmouse();
void  invert_label(int, MENUITEM *);
int   enhanced_keybrd(void);
int   dont_interrupt(void);
int   interrupt_ok(void);
int   kbseq(int *);
int   specialkey(unsigned int);
void  do_alarm_signal(void);
void  do_hup_signal(void);
char *pfnexpand(char *, int);
int   ssleep(long);
int   sleep(int);


/*
 * Useful global def's
 */
static int    enhncd = 0;		/* keyboard of enhanced variety? */
union  REGS   rg;
struct SREGS  segreg;

static	int oldbut;			/* Previous state of mouse buttons */
static	unsigned short oldbreak;	/* Original state of break key */
static	char ptmpfile[128];		/* popen temp file */


/*
 * Include generic DOS/Windows routines
 */
#include	"dos_gen.c"


/*
 * DISable ctrl-break interruption
 */
dont_interrupt()
{
    /* get original value, to be restored later... */
    rg.h.ah = 0x33;		/* control-break check dos call */
    rg.h.al = 0;		/* get the current state */
    rg.h.dl = 0;		/* pre-set it OFF */
    intdos(&rg, &rg);		/* go for it! */
    oldbreak = rg.h.dl;
    /* kill the ctrl-break interupt */
    rg.h.ah = 0x33;		/* control-break check dos call */
    rg.h.al = 1;		/* set the current state */
    rg.h.dl = 0;		/* set it OFF */
    intdos(&rg, &rg);		/* go for it! */
}


/*
 * re-enable ctrl-break interruption
 */
interrupt_ok()
{
    /* restore the ctrl-break interupt */
    rg.h.ah = 0x33;		/* control-break check dos call */
    rg.h.al = 1;		/* set to new state */
    rg.h.dl = oldbreak;		/* set it to its original value */
    intdos(&rg, &rg);	/* go for it! */
}


/*
 * return true if an enhanced keyboard is present
 */
enhanced_keybrd()
{
    /* and check for extended keyboard */
    rg.h.ah = 0x05;
    rg.x.cx = 0xffff;
    int86(BIOS_KEYBRD, &rg, &rg);
    rg.h.ah = 0x10;
    int86(BIOS_KEYBRD, &rg, &rg);
    return(rg.x.ax == 0xffff);
}


/*
 * This function is called once to set up the terminal device streams.
 */
ttopen()
{
    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
/* 
 * init_mouse - check for and initialize mouse driver...
 */
init_mouse()
{
    long miaddr;		/* mouse interupt routine address */

    if(mexist)
      return(TRUE);

    /* check if the mouse drive exists first */
    rg.x.ax = 0x3533;		/* look at the interrupt 33 address */
    intdosx(&rg, &rg, &segreg);
    miaddr = (((long)segreg.es) << 16) + (long)rg.x.bx;
    if (miaddr == 0 || *(char *)miaddr == 0xcf) {
	mexist = FALSE;
	return(TRUE);
    }

    /* and then check for the mouse itself */
    rg.x.ax = 0;			/* mouse status flag */
    int86(BIOS_MOUSE, &rg, &rg);	/* check for the mouse interupt */
    mexist = (rg.x.ax != 0);
    nbuttons = rg.x.bx;

    if (mexist == FALSE)
	return(TRUE);

    /* if the mouse exists.. get it in the upper right corner */
    rg.x.ax = 4;			/* set mouse cursor position */
    rg.x.cx = (term.t_ncol/2) << 3;	/* Center of display... */
    rg.x.dx = 1 << 3;			/* Second line down */
    int86(BIOS_MOUSE, &rg, &rg);

    /* and set its attributes */
    rg.x.ax = 10;		/* set text cursor */
    rg.x.bx = 0;		/* software text cursor please */
    rg.x.cx = 0x77ff;	/* screen mask */
    rg.x.dx = 0x7700;	/* cursor mask */
    int86(BIOS_MOUSE, &rg, &rg);
    return(TRUE);
}


/*
 * mouseon - call made available for programs calling pico to turn ON the
 *           mouse cursor.
 */
void
mouseon()
{
    rg.x.ax = 1;			/* Show Cursor */
    int86(BIOS_MOUSE, &rg, &rg); 
}


/*
 * mouseon - call made available for programs calling pico to turn OFF the
 *           mouse cursor.
 */
void
mouseoff()
{
    rg.x.ax = 2;			/* Hide Cursor */
    int86(BIOS_MOUSE, &rg, &rg);
}
#endif


/*
 * This function gets called just before we go back home to the command
 * interpreter.
 */
ttclose()
{
    if(!Pmaster)
      interrupt_ok();

    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(K_PAD_UP);
	case 0x5000 : return(K_PAD_DOWN);
	case 0x4b00 : return(K_PAD_LEFT);
	case 0x4d00 : return(K_PAD_RIGHT);
	case 0x4700 : return(K_PAD_HOME);
	case 0x4f00 : return(K_PAD_END);
	case 0x4900 : return(K_PAD_PREVPAGE);
	case 0x5100 : return(K_PAD_NEXTPAGE);
	case 0x5300 : return(K_PAD_DELETE);
	case 0x48e0 : return(K_PAD_UP);			/* grey key version */
	case 0x50e0 : return(K_PAD_DOWN);		/* grey key version */
	case 0x4be0 : return(K_PAD_LEFT);		/* grey key version */
	case 0x4de0 : return(K_PAD_RIGHT);		/* grey key version */
	case 0x47e0 : return(K_PAD_HOME);		/* grey key version */
	case 0x4fe0 : return(K_PAD_END);		/* grey key version */
	case 0x49e0 : return(K_PAD_PREVPAGE);		/* grey key version */
	case 0x51e0 : return(K_PAD_NEXTPAGE);		/* grey key version */
	case 0x53e0 : return(K_PAD_DELETE);		/* 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(_bios_keybrd(enhncd ? _NKEYBRD_READ : _KEYBRD_READ));
}


/*
 * ctrlkey - used to check if the key hit was a control key.
 */
ctrlkey()
{
    return(_bios_keybrd(enhncd ? _NKEYBRD_SHIFTSTATUS : _KEYBRD_SHIFTSTATUS)
            & 0x04);
}


/*
 * win_multiplex - give DOS in a window a shot at the CPU
 */
win_multiplex()
{
    rg.x.ax = 0x1680;
    int86(DOS_MULTIPLEX, &rg, &rg);
}


/*
 * 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;

    if(mexist || timeout){
	timein = time(0L);
#ifdef	MOUSE
	if(mexist){
	    rg.x.ax = 1;			/* Show Cursor */
	    int86(BIOS_MOUSE, &rg, &rg); 
	}
#endif
	while(!_bios_keybrd(enhncd ? _NKEYBRD_READY : _KEYBRD_READY)){
#if	MOUSE
	    if(timeout && time(0L) >= timein+timeout){
		if(mexist){
		    rg.x.ax = 2;		/* Hide Cursor */
		    int86(BIOS_MOUSE, &rg, &rg);
		}
		return(NODATA);
	    }

	    if(checkmouse(&ch)){		/* something happen ?? */
		if(mexist){
		    rg.x.ax = 2;		/* Hide Cursor */
		    int86(BIOS_MOUSE, &rg, &rg);
		}
		curwp->w_flag |= WFHARD;
		return(ch);
	    }
#else
	    if(time(0L) >= timein+timeout)
	      return(NODATA);
#endif	/* MOUSE */

	    /*
	     * Surrender the CPU...
	     */
	    if(!intrupt++)
	      win_multiplex();
        }
#ifdef	MOUSE
	if(mexist){
	    rg.x.ax = 2;			/* Hide Cursor */
	    int86(BIOS_MOUSE, &rg, &rg);
	}
#endif	/* MOUSE */
    }

    ch  = (*term.t_getchar)();
    lch = (ch&0xff);
    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)
unsigned *ch;
{
    register int k;		/* current bit/button of mouse */
    int mcol;			/* current mouse column */
    int mrow;			/* current mouse row */
    int sstate;			/* current shift key status */
    int newbut;			/* new state of the mouse buttons */
    int rv = 0;

    if(!mexist)
	return(FALSE);

    /* check to see if any mouse buttons are different */
    rg.x.ax = 3;		/* Get button status and mouse position */
    int86(BIOS_MOUSE, &rg, &rg);
    newbut = rg.x.bx;
    mcol = rg.x.cx >> 3;
    mrow = (rg.x.dx >> 3);

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

    if (mcol < 0)		/* only on screen presses are legit! */
	mcol = 0;
    if (mrow < 0)
	mrow = 0;

    sstate = 0;			/* get the shift key status as well */
    rg.h.ah = 2;
    int86(BIOS_KEYBRD, &rg, &rg);
    sstate = rg.h.al;

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

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

		if(mfunc.action && M_ACTIVE(mrow, mcol, &mfunc)){
		    unsigned long r;

		    if((r = (*mfunc.action)(newbut&k, mrow, mcol))&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(mrow, mcol, &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);
	}
    }

    return(FALSE);
}


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

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

    rg.h.ah = 3;				/* get cursor position */
    int86(BIOS_VIDEO, &rg, &rg);
    p = rg.h.bh;
    c = rg.h.dl;
    r = rg.h.dh;
    rg.x.ax = 2;				/* Hide Cursor */
    int86(BIOS_MOUSE, &rg, &rg);
    (*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);
    rg.h.ah = 2;
    rg.h.bh = p;
    rg.h.dh = r;
    rg.h.dl = c;
    int86(BIOS_VIDEO, &rg, &rg);		/* restore old position */
    rg.x.ax = 1;				/* Show Cursor */
    int86(BIOS_MOUSE, &rg, &rg);
}
#endif	/* MOUSE */


/*
 * alt_editor - fork off an alternate editor for mail message composition
 *
 *  NOTE: Not yet used under DOS
 */
alt_editor(f, n)
{
    return(-1);
}


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

    (*term.t_move)(term.t_nrow, 0);
    if(system((shell = getenv("COMSPEC")) ? shell : "command") == -1)
      emlwrite("Error loading %s", shell ? shell : "COMMAND.COM");
    else
      refresh(0, 1);			/* redraw */
}


/*
 * 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;
{
    char cmdbuf[NLINE];
    sprintf(ptmpfile, tmpnam(NULL));
    sprintf(cmdbuf, "%s > %s", c, ptmpfile);
    if(system(cmdbuf) == -1){
	unlink(ptmpfile);
	return(NULL);
    }

    return(fopen(ptmpfile, "r"));
}



/*
 * P_close - close the given descriptor
 *
 */
P_close(fp)
FILE *fp;
{
    fclose(fp);			/* doesn't handle return codes */
    unlink(ptmpfile);
    return(0);;
}
