/* mdraw.c - Mouse handler demo program */

/************************************************************************/
/*	Copyright (C) 1986-1993 Phar Lap Software, Inc.			*/
/*	Unpublished - rights reserved under the Copyright Laws of the	*/
/*	United States.  Use, duplication, or disclosure by the 		*/
/*	Government is subject to restrictions as set forth in 		*/
/*	subparagraph (c)(1)(ii) of the Rights in Technical Data and 	*/
/*	Computer Software clause at 252.227-7013.			*/
/*	Phar Lap Software, Inc., 60 Aberdeen Ave., Cambridge, MA 02138	*/
/************************************************************************/

//	
//	This program initalizes the display and the mouse, and
//	then sits in a loop waiting for mouse buttons to be pressed.
//
//	NOTE:	By default, this program copies the real mode code and data
//		to conventional memory using the realcopy() function.  For
//		this to work, the program MUST be linked with the 
//		-MINREAL 400h switch, to guarantee that some conventional 
//		memory is left free for the real mode code and data.
//
//		Alternatively, you can build this program with the -REALBREAK
//		switch to have 386|DOS-Extender automatically load the
//		real mode code and data in conventional memory.  The use
//		of -REALBREAK is not recommended, because while it is 
//		simpler, it is less compatible than using realcopy().
//		Specifically, to use -REALBREAK under Windows 3.x, you
//		must install the PHARLAP.386 VxD, and under OS/2 2.0
//		you must get the OS/2 kernel update in the OS/2 Service
//		Pack from IBM.  There may also be other compatibility
//		problems in the future with other DPMI host operating systems.
//		To build this program to use -REALBREAK, you must
//		(1) compile this file with the symbol REALBREAK defined,
//		and (2) link with the -REALBREAK EndReal switch.
//
//	The program is always either in "character mode" or 
//	"toggle mode", and it starts out in character mode, with
//	the current character set to '*'.  You can use this program to
//	draw rudimentary pictures with '*' characters, toggling to ' ' 
//	characters to erase mistakes.
//
//	character mode:
//		left button:	enter toggle mode
//		right button:	output character at cursor position
//
//	toggle mode:
//		left button:	exit program
//		right button:	toggle output char (between '*' and ' '), and
//				switch to character mode
//

#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <pharlap.h>
#include <pldos32.h>

#ifdef __WATCOMC__
#define _REGS REGS
#define _int86 int386
// Names of regs in _REGS union are nonstandard
#define ax eax
#endif

//
// Global data
//
UCHAR	OrigVidMode;		// original video mode at startup time
USHORT	RealSegAdr;		// real mode segment addr of allocated memory
extern ULONG ProtMouHandp;	// offset of ProtMouHand()
USHORT	ProtScreenSeg;		// protected mode screen selector
BOOL	Donef;			// T ==> done with program
BOOL	CharModef;		// T ==> Character Mode, F ==> Toggle Mode
UCHAR	OutChar;		// character to output

//
// Global data in the real mode segment
//
extern REALPTR CallProtFuncp;	// addr of routine to use to call from real
				// mode to protected mode
extern USHORT ProtCodeSeg;	// protected mode CS selector

//
// Function prototypes
//
BOOL Init(void);
void Cleanup(void);
void __STKCALL ProtMouHand(ULONG NumParams, ULONG CondMask, 
						ULONG CursorX, ULONG CursorY);
BOOL realcopy(ULONG start_offs, ULONG end_offs, REALPTR *real_basep, 
				FARPTR *prot_basep, USHORT *rmem_adrp);
BOOL RealBreak(ULONG FuncOffs, ULONG EndOffs, REALPTR *RealFuncpp);

//
// main - Main routine of MDRAW program.
//
int main()
{
//
// Initialize mouse and install mouse handler, and init globals
//
	if (Init())
		return 1;

//
// Spin until global done flag is set by the mouse handler.
//
	while (!Donef)
	{}

//
// De-install mouse handler and exit
//
	Cleanup();
	return 0;
}

//
// Init - Initialize mouse handler
//	Returns TRUE if error, FALSE if success
//
BOOL Init(void)
{
	union _REGS InRegs;	// regs input to system call
	union _REGS OutRegs;	// regs output from system call
	SWI_REGS RealRegs;	// real mode registers for _dx_real_int() call
	REALPTR	RealBufAdr;	// real mode address of DOS-X buffer (unused)
	FARPTR	ProtBufAdr;	// prot mode address of DOS-X buffer (unused)
	ULONG	BufSize;	// size of DOS-X buffer (unused)
	CONFIG_INF config;	// 386|DOS-Extender config info
	UCHAR	buf[256];	// buffer needed for _dx_config_inf() call
	REALPTR	Int33Vec;	// real mode INT 33h interrupt vector
	UCHAR	Opcode;		// opcode of 1st instruction in INT 33h handler
	REALPTR	RealMouHandp;	// real mode FAR ptr to RealMouHand()
	extern void StartReal(),EndReal(); // start and end of real mode code
	extern void RealMouHand(); // real mode code mouse handler

//
// Initialize globals
//
	CharModef = TRUE;		
	OutChar = '*';
	Donef = FALSE;
	_dx_rmlink_get(&CallProtFuncp, &RealBufAdr, &BufSize, &ProtBufAdr);
	_dx_config_inf(&config, buf);
	ProtCodeSeg = config.c_cs_sel;
	ProtScreenSeg = config.c_vidtxt_sel;
	ProtMouHandp = (ULONG) ProtMouHand;

//
// Save the current video mode, and set the video mode to 80x25 B&W
// (8x8 character cell)
//
	InRegs.h.ah = 0x0F;
	_int86(0x10, &InRegs, &OutRegs);
	OrigVidMode = OutRegs.h.al;
	InRegs.h.ah = 0x00;
	InRegs.h.al = 0x02;		// 80x25 B&W
	_int86(0x10, &InRegs, &OutRegs);

//
// Check for presence of mouse driver, take error exit if not present,
// initialize it if present
//
	_dx_rmiv_get(0x33, &Int33Vec);
	if (RP_SEG(Int33Vec) == 0)
		goto NoMouseDriver;
	Opcode = PeekRealByte(Int33Vec);
	if (Opcode == 0xCF)	// IRET instruction
		goto NoMouseDriver;

	InRegs.x.ax = 0;	// initialize mouse driver
	_int86(0x33, &InRegs, &OutRegs);
	if (OutRegs.h.ah == 0 && OutRegs.h.al == 0)
		goto NoMouseDriver;
	if (OutRegs.h.bl < 2)
		goto OneButton;

//
// Make sure real mode code and data is in conventional memory, and
// get the real mode address of the mouse handler
//
#ifdef REALBREAK
	if (RealBreak((ULONG) RealMouHand, (ULONG) EndReal, &RealMouHandp))
		return TRUE;
#else
	{
	REALPTR RealBaseAdr;	// Real mode base addr of code in real mode mem
	FARPTR	ProtBaseAdr;	// Prot mode base addr of code in real mode mem
	if (realcopy((ULONG) StartReal, (ULONG) EndReal, &RealBaseAdr,
					&ProtBaseAdr, &RealSegAdr))
		return TRUE;
	RealMouHandp = RealBaseAdr + (REALPTR) RealMouHand;
	}
#endif

//
// Install our real mode mouse handler, and display the cursor
// To install the real mode handler, we can't use _int86x(), which would
// load ES in protected mode;  we have to use _dx_real_int(), which
// will load ES in real mode.
//
	RealRegs.es = RP_SEG(RealMouHandp);
	RealRegs.edx = (ULONG) RP_OFF(RealMouHandp);
	RealRegs.eax = 12;	// install handler
	RealRegs.ecx = 0xA;	// mask:  invoke handler when left or right
				// 	  mouse button is pressed
	_dx_real_int(0x33, &RealRegs);

	InRegs.x.ax = 1;	// display mouse cursor
	_int86(0x33, &InRegs, &OutRegs);

	return FALSE;

NoMouseDriver:
	printf("No mouse driver installed\n");
	return TRUE;

OneButton:
	printf("This program requires a mouse with two buttons\n");
	return TRUE;
}

//
// Cleanup - de-install mouse handler
//
void Cleanup(void)
{
	union _REGS InRegs;	// regs input to system call
	union _REGS OutRegs;	// regs output from system call

//
// Reset the mouse driver to de-install our handler
//
	InRegs.x.ax = 0;
	_int86(0x33, &InRegs, &OutRegs);

//
// Restore the original video mode
//
	InRegs.h.ah = 0;
	InRegs.h.al = OrigVidMode;
	_int86(0x10, &InRegs, &OutRegs);

//
// Free up the conventional (real mode) memory we allocated for our
// mouse handler
//
#ifndef REALBREAK
	_dx_real_free(RealSegAdr);
#endif

	return;
}

//
// Protected mode mouse handler
// Force stack-based calling conventions, and turn off any stack overflow
// checking.
//
#ifndef __MSC32__
#pragma off(check_stack);
#endif
void __STKCALL ProtMouHand(ULONG NumParams, ULONG CondMask, 
						ULONG CursorX, ULONG CursorY)
{
	ULONG	ScreenOffs;	// offset in display of cursor position
	FARPTR	FarScreenp;	// FAR ptr to a byte on the screen

	if (CharModef)
	{
		// In Character Mode:
		//	left button:	enter toggle mode
		//	right button:	output character at cursor position
		if (CondMask & 2)	// left button pressed
			CharModef = FALSE;	
		else		
		{
			// convert pixel coords to byte offset in screen
			// We have an 8x8 character cell in this display mode
			ScreenOffs = CursorY/8 * 160 + CursorX/8 * 2;
			FP_SET(FarScreenp, ScreenOffs, ProtScreenSeg);
			PokeFarByte(FarScreenp, OutChar);
		}
	}
	else
	{
		// in Toggle Mode:
		//	left button:	set "done" flag
		//	right button:	toggle output char ('*' <==> ' ') and
		//			switch to character mode
		if (CondMask & 2)	// left button pressed
			Donef = TRUE;
		else			
		{
			OutChar = (OutChar == '*') ? ' ' : '*';
			CharModef = TRUE;
		}
	}
	return;
}
