/* mouse.c for the DOS emulator
 *       Robert Sanders, gt8134b@prism.gatech.edu
 *
 */

#include "init.h"
#include "emu.h"
#include "bios.h"
#include "int.h"
#include "memory.h"
#include "video.h"		/* video base address */
#include "serial.h"
#include "timers.h"
#include "virq.h"
#include "coopth.h"
#include "utilities.h"
#include "doshelpers.h"
#include "lowmem.h"
#include "mouse.h"

/* This is included for video mode support. Please DO NOT remove !
 * mousevid.h will become part of VGA emulator package */
#include "mousevid.h"
#include "gcursor.h"
#include "vgaemu.h"

#define SETHIGH(x, v) HI_BYTE_d(x) = (v)
#define SETLO_WORD(x, v) LO_WORD(x) = (v)
#define SETLO_BYTE(x, v) LO_BYTE_d(x) = (v)
#define SETWORD(x, v) SETLO_WORD(x, v)

#define MOUSE_RX mouse_roundx(get_mx())
#define MOUSE_RY mouse_roundy(get_my())
#define MOUSE_MINX 0
#define MOUSE_MINY 0
#define INIT_SPEED_X 8
#define INIT_SPEED_Y 16

static int mouse_tid;

static void reset_scale(void);

static int mickeyx(void)
{
	return (mouse.unscm_x / (mouse.px_range * 8));
}

static int mickeyy(void)
{
	return (mouse.unscm_y / (mouse.py_range * 8));
}

static int _get_mx(void)
{
	return mouse.unsc_x / mouse.px_range;
}

static int get_mx(void)
{
	static int warned;
	int ret = _get_mx();
	if (mouse.virtual_maxx && ret > mouse.virtual_maxx && !warned) {
		dosemu_error("MOUSE: X out of range: %i > %i, %i\n",
				ret, mouse.virtual_maxx, mouse.px_range);
		warned++;
	}
	return ret;
}

static int _get_my(void)
{
	return mouse.unsc_y / mouse.py_range;
}

static int get_my(void)
{
	static int warned;
	int ret = _get_my();
	if (mouse.virtual_maxy && ret > mouse.virtual_maxy && !warned) {
		dosemu_error("MOUSE: Y out of range: %i > %i, %i\n",
				ret, mouse.virtual_maxy, mouse.py_range);
		warned++;
	}
	return ret;
}

static void set_px_ranges(int x_range, int y_range)
{
	/* need to update unscaled coords */
	mouse.unsc_x = mouse.unsc_x * x_range / mouse.px_range;
	mouse.unsc_y = mouse.unsc_y * y_range / mouse.py_range;
	mouse.unscm_x = mouse.unscm_x * x_range / mouse.px_range;
	mouse.unscm_y = mouse.unscm_y * y_range / mouse.py_range;

	m_printf("MOUSE: setting px ranges (%i, %i) -> (%i, %i)\n",
			mouse.px_range, mouse.py_range, x_range, y_range);
	mouse.px_range = x_range;
	mouse.py_range = y_range;
}

static
void mouse_cursor(int), mouse_pos(void), mouse_setpos(void),
 mouse_setxminmax(void), mouse_setyminmax(void), mouse_set_tcur(void),
 mouse_set_gcur(void), mouse_setsub(void), mouse_bpressinfo(void), mouse_brelinfo(void),
 mouse_mickeys(void), mouse_version(void), mouse_enable_internaldriver(void),
 mouse_disable_internaldriver(void),
 mouse_software_reset(void),
 mouse_getgeninfo(void), mouse_exclusionarea(void), mouse_setcurspeed(void),
 mouse_storestate(void), mouse_restorestate(void),
 mouse_getmaxcoord(void), mouse_getstorereq(void), mouse_setsensitivity(void),
 mouse_detsensitivity(void), mouse_detstatbuf(void), mouse_excevhand(void),
 mouse_largecursor(void), mouse_doublespeed(void), mouse_alternate(void),
 mouse_detalternate(void), mouse_hardintrate(void), mouse_disppage(void),
 mouse_detpage(void), mouse_getmaxminvirt(void);
static void scale_coords3(int x, int y, int x_range, int y_range,
	int speed_x, int speed_y, long long *s_x, long long *s_y);
static void scale_coords(int x, int y, int x_range, int y_range,
	long long *s_x, long long *s_y);
static void scale_coords_spd_unsc(int x, int y, long long *s_x, long long *s_y);
static void scale_coords_spd_unsc_mk(int x, int y, long long *s_x,
	long long *s_y);
static void do_move_abs(int x, int y, int x_range, int y_range, int vis);

/* mouse movement functions */
static void mouse_reset(void);
static void mouse_do_cur(int callback), mouse_update_cursor(void);
static void mouse_reset_to_current_video_mode(void);

static void int33_mouse_move_buttons(int lbutton, int mbutton, int rbutton, void *udata);
static void int33_mouse_move_button(int num, int press, void *udata);
static void int33_mouse_move_wheel(int dy, void *udata);
static void int33_mouse_move_relative(int dx, int dy, int x_range, int y_range, void *udata);
static void int33_mouse_move_mickeys(int dx, int dy, void *udata);
static void int33_mouse_move_absolute(int x, int y, int x_range, int y_range,
    int vis, void *udata);
static void int33_mouse_drag_to_corner(int x_range, int y_range, void *udata);
static void int33_mouse_enable_native_cursor(int flag, void *udata);
static void call_int15_mouse_event_handler(void);

/* graphics cursor */
void graph_cursor(void), text_cursor(void);
void graph_plane(int);

/* called when mouse changes */
static void mouse_delta(int);

/* Internal mouse helper functions */
static int mouse_clip_coords(void);
static void mouse_hide_on_exclusion(void);

static int mouse_events = 0;
struct dragged_hack {
  int cnt, skipped;
  int x, y, x_range, y_range;
} dragged;
static mouse_erase_t mouse_erase;
static struct mousevideoinfo mouse_current_video;

#define mice (&config.mouse)
struct mouse_struct mouse;

static inline int mouse_roundx(int x)
{
	return x & ~((1 << mouse.xshift)-1);
}

static inline int mouse_roundy(int y)
{
	return y & ~((1 << mouse.yshift)-1);
}

static unsigned short default_graphcursormask[HEIGHT] =  {
  0x0000,  /*0000000000000000*/
  0x4000,  /*0100000000000000*/
  0x6000,  /*0110000000000000*/
  0x7000,  /*0111000000000000*/
  0x7800,  /*0111100000000000*/
  0x7c00,  /*0111110000000000*/
  0x7e00,  /*0111111000000000*/
  0x7f00,  /*0111111100000000*/
  0x7f80,  /*0111111110000000*/
  0x7f00,  /*0111111100000000*/
  0x7c00,  /*0111110000000000*/
  0x4600,  /*0100011000000000*/
  0x0600,  /*0000011000000000*/
  0x0300,  /*0000001100000000*/
  0x0300,  /*0000001100000000*/
  0x0000   /*0000000000000000*/
};

static unsigned short default_graphscreenmask[HEIGHT] =  {
  0x3fff,  /*0011111111111111*/
  0x1fff,  /*0001111111111111*/
  0x0fff,  /*0000111111111111*/
  0x07ff,  /*0000011111111111*/
  0x03ff,  /*0000001111111111*/
  0x01ff,  /*0000000111111111*/
  0x00ff,  /*0000000011111111*/
  0x007f,  /*0000000001111111*/
  0x003f,  /*0000000000111111*/
  0x007f,  /*0000000001111111*/
  0x01ff,  /*0000000111111111*/
  0x10ff,  /*0001000011111111*/
  0xb0ff,  /*1011000011111111*/
  0xf87f,  /*1111100001111111*/
  0xf87f,  /*1111100001111111*/
  0xfcff   /*1111110011111111*/
};

void
mouse_helper(struct vm86_regs *regs)
{
  if (!mice->intdrv) {
    m_printf("MOUSE No Internaldriver set, exiting mouse_helper()\n");
    SETWORD(regs->eax, 0xffff);
    return;
  }

  SETWORD(regs->eax, 0);		/* Set successful completion */

  switch (LO_BYTE_d(regs->ebx)) {
  case 0:				/* Reset iret for mouse */
    m_printf("MOUSE move iret !\n");
    mouse_enable_internaldriver();
    break;
  case 1:				/* Select Microsoft Mode */
    m_printf("MOUSE Microsoft Mouse (two buttons) selected.\n");
    mouse.threebuttons = FALSE;
    break;
  case 2:				/* Select PC Mouse (3 button) */
    /* enabling this if you only have a two button mouse and didn't
    	set emulate3buttons is silly.  however, if your mouse really
    	does have three buttons, you want to be able to use them. */
    m_printf("MOUSE PC Mouse (three buttons) selected.\n");
    mouse.threebuttons = TRUE;
    break;
  case 3:				/* Tell me what mode we are in ? */
    if (!mouse.threebuttons)
      SETHIGH(regs->ebx, 0x10);		/* We are currently in Microsoft Mode */
    else
      SETHIGH(regs->ebx, 0x20);		/* We are currently in PC Mouse Mode */
    SETLO_BYTE(regs->ecx, mice->init_speed_x);
    SETHIGH(regs->ecx, mice->init_speed_y);
    SETLO_BYTE(regs->edx, (mice->ignorevesa & 1) |
	((mice->ignore_speed << 1) & 2));
    break;
  case 4:				/* Set vertical speed */
    if (LO_BYTE_d(regs->ecx) < 1) {
      m_printf("MOUSE Vertical speed out of range. ERROR!\n");
      SETWORD(regs->eax, 1);
    } else
      mice->init_speed_y = LO_BYTE_d(regs->ecx);
    break;
  case 5:				/* Set horizontal speed */
    if (LO_BYTE_d(regs->ecx) < 1) {
      m_printf("MOUSE Horizontal speed out of range. ERROR!\n");
      SETWORD(regs->eax, 1);
    } else
      mice->init_speed_x = LO_BYTE_d(regs->ecx);
    break;
  case 6:				/* Ignore vesa modes */
    mice->ignorevesa = LO_BYTE_d(regs->ecx);
    break;
  case 7:				/* get minimum internal resolution */
    SETWORD(regs->ecx, mouse.min_max_x);
    SETWORD(regs->edx, mouse.min_max_y);
    break;
  case 8:				/* set minimum internal resolution */
    mouse.min_max_x = LO_WORD(regs->ecx);
    mouse.min_max_y = LO_WORD(regs->edx);
    reset_scale();
    break;
  case 9:				/* set cursor visibility */
    mouse_client_show_cursor(LO_BYTE_d(regs->ecx));
    break;
  case 0xa:				/* lock cursor visibility */
    mouse.visibility_locked = LO_BYTE_d(regs->ecx);
    break;
  case 0xb:				/* ungrabbed mode tweak */
    mice->ignore_speed = LO_BYTE_d(regs->ecx);
    break;
  case DOS_SUBHELPER_MOUSE_START_VIDEO_MODE_SET:
    m_printf("MOUSE Start video mode set\n");
    /* make sure cursor gets turned off */
    mouse_cursor(-1);
    break;
  case DOS_SUBHELPER_MOUSE_END_VIDEO_MODE_SET:
    m_printf("MOUSE End video mode set\n");
    {
      /* redetermine the video mode:
         the stack contains: mode, saved ax, saved bx */
      unsigned int ssp = SEGOFF2LINEAR(regs->ss, 0);
      unsigned int sp = (regs->esp + 2 + 6) & 0xffff;
      unsigned ax = popw(ssp, sp);
      int mode = popw(ssp, sp);

      if (!mice->ignorevesa && mode >= 0x100 &&
	    (mode & 0xff00) != 0x1100 && ax == 0x4f) {
	/* no chargen function; check if vesa mode set successful */
	vidmouse_set_video_mode(mode);
      } else if ((mode & 0xff00) != 0x1100) {
	vidmouse_set_video_mode(-1);
      }
      mouse_reset_to_current_video_mode();
    }
    /* replace cursor if necessary */
    mouse_cursor(1);
    /* reset hide count on mode switches to fix this:
     * https://github.com/stsp/dosemu2/issues/314
     */
    if (mouse.cursor_on < -1) {
	m_printf("MOUSE: normalizing hide count, %i\n", mouse.cursor_on);
	mouse.cursor_on = -1;
    }
    break;
  case 0xf2:
    m_printf("MOUSE int74 helper\n");
    if (mouse.ps2.state && (mouse.ps2.cs || mouse.ps2.ip)) {
      call_int15_mouse_event_handler();
      mouse_events = 0;
    }
    pic_untrigger(12);
    break;
  case 0xff:
    m_printf("MOUSE Checking InternalDriver presence !!\n");
    break;
  default:
    m_printf("MOUSE Unknown mouse_helper function\n");
    SETWORD(regs->eax, 1);		/* Set unsuccessful completion */
  }
}

void mouse_ps2bios(void)
{
  m_printf("PS2MOUSE: Call ax=0x%04x, bx=0x%04x\n", LWORD(eax), LWORD(ebx));
  if (!mice->intdrv) {
    REG(eax) = 0x0500;        /* No ps2 mouse device handler */
    CARRY;
    return;
  }

  switch (REG(eax) &= 0x00FF) {
  case 0x0000:
    mouse.ps2.state = HI(bx);
    HI(ax) = 0;
    NOCARRY;
    break;
  case 0x0001:
    mouse.ps2.state = 0;
    HI(ax) = 0;
    LWORD(ebx) = 0xAAAA;    /* we have a ps2 mouse */
    NOCARRY;
    break;
  case 0x0002:
    HI(ax) = 0;
    NOCARRY;
    break;
  case 0x0003:
    if (HI(bx) > 3) {
      CARRY;
      HI(ax) = 1;
    } else {
      NOCARRY;
      HI(ax) = 0;
    }
    break;
  case 0x0004:
    HI(bx) = 0xAA;
    HI(ax) = 0;
    NOCARRY;
    break;
  case 0x0005:			/* Initialize ps2 mouse */
    HI(ax) = 0;
    mouse.ps2.state = 0;
    mouse.ps2.pkg = HI(bx);
    NOCARRY;
    break;
  case 0x0006:
    switch (HI(bx)) {
    case 0x00:
      LO(bx)  = (mouse.rbutton ? 1 : 0);
      LO(bx) |= (mouse.lbutton ? 4 : 0);
      LO(bx) |= 0; 	/* scaling 1:1 */
      LO(bx) |= 0x20;	/* device enabled */
      LO(bx) |= 0;	/* stream mode */
      LO(cx) = 0;		/* resolution, one count */
      LO(dx) = 0;		/* sample rate */
      HI(ax) = 0;
      NOCARRY;
      break;
    case 0x01:
      HI(ax) = 0;
      NOCARRY;
      break;
    case 0x02:
      HI(ax) = 1;
      CARRY;
      break;
    }
    break;
  case 0x0007:
    m_printf("PS2MOUSE: set device handler %04x:%04x\n", SREG(es), LWORD(ebx));
    mouse.ps2.cs = SREG(es);
    mouse.ps2.ip = LWORD(ebx);
    HI(ax) = 0;
    NOCARRY;
    break;
  default:
    HI(ax) = 1;
    g_printf("PS2MOUSE: Unknown call ax=0x%04x\n", LWORD(eax));
    CARRY;
  }
}

int
mouse_int(void)
{
  if (!mice->intdrv)
    return 0;
  m_printf("MOUSE: int 33h, ax=%x bx=%x\n", LWORD(eax), LWORD(ebx));

  switch (LWORD(eax)) {
  case 0x00:			/* Mouse Reset/Get Mouse Installed Flag */
    mouse_reset();
    break;

  case 0x01:			/* Show Mouse Cursor */
    mouse_cursor(1);
    break;

  case 0x02:			/* Hide Mouse Cursor */
    mouse_cursor(-1);
    break;

  case 0x03:			/* Get Mouse Position and Button Status */
    mouse_pos();
    break;

  case 0x04:			/* Set Cursor Position */
    mouse_setpos();
    break;

  case 0x05:			/* Get mouse button press info */
    mouse_bpressinfo();
    break;

  case 0x06:			/* Get mouse button release info */
    mouse_brelinfo();
    break;

  case 0x07:			/* Set Mouse Horizontal Min/Max Position */
    mouse_setxminmax();
    break;

  case 0x08:			/* Set Mouse Vertical Min/Max Position */
    mouse_setyminmax();
    break;

  case 0x09:			/* Set mouse gfx cursor */
    mouse_set_gcur();
    break;

  case 0x0A:			/* Set mouse text cursor */
    mouse_set_tcur();
    break;

  case 0x0B:			/* Read mouse motion counters */
    mouse_mickeys();
    break;

  case 0x0C:			/* Set mouse subroutine */
    mouse_setsub();
    break;

  case 0x0D:			/* Enable Light Pen */
    break;

  case 0x0E:			/* Disable Light Pen */
    break;

  case 0x0F:			/* Set cursor speed */
    mouse_setcurspeed();
    break;

  case 0x10:			/* Define mouse exclusion zone */
    mouse_exclusionarea();
    break;

  case 0x11:
    LWORD(eax) = 0x574D;
    LWORD(ebx) = 0;
    LWORD(ecx) = 1;
    break;

  case 0x12:			/* Set Large Graphics Cursor Block */
    mouse_largecursor();
    break;

  case 0x13:			/* Set Maximum for mouse speed doubling */
    mouse_doublespeed();
    break;

  case 0x14:			/* Exchange Event Handler */
    mouse_excevhand();
    break;

  case 0x15:			/* Get size of buffer needed to hold state */
    mouse_detstatbuf();
    break;

  case 0x16:			/* Save mouse driver state */
    mouse_storestate();
    break;

  case 0x17:			/* Restore mouse driver state */
    mouse_restorestate();
    break;

  case 0x18:			/* Install alternate event handler */
    mouse_alternate();
    break;

  case 0x19:			/* Determine address of alternate */
    mouse_detalternate();
    break;

  case 0x1A:			/* Set mouse sensitivity */
    mouse_setsensitivity();
    break;

  case 0x1B:			/* Determine mouse sensitivity */
    mouse_detsensitivity();
    break;

  case 0x1C:			/* Set mouse hardware interrupt rate */
    mouse_hardintrate();
    break;

  case 0x1D:			/* Set display page */
    mouse_disppage();
    break;

  case 0x1E:			/* Determine display page */
    mouse_detpage();
    break;

  case 0x1F:			/* Disable Mouse Driver */
    mouse_disable_internaldriver();
    break;

  case 0x20:			/* Enable Mouse Driver */
    mouse_enable_internaldriver();
    break;

  case 0x21:
    mouse_software_reset();	/* Perform Software reset on mouse */
    break;

  case 0x22:			/* Set language for messages */
    m_printf("MOUSE: Set LANGUAGE, ignoring.\n");
    break;			/* Ignore as we are US version only */

  case 0x23:			/* Get language for messages */
    m_printf("MOUSE: Get LANGUAGE\n");
    LWORD(ebx) = 0x0000;	/* Return US/ENGLISH */
    break;

  case 0x24:			/* Get driver version/mouse type/IRQ */
    mouse_version();
    break;

  case 0x25:			/* Get general driver information */
    mouse_getgeninfo();
    break;

  case 0x26:			/* Return Maximal Co-ordinates */
    mouse_getmaxcoord();
    break;

#if 0
  case 0x27:			/* Get masks and mickey counts */
    mouse_getmasksmicks();	/* TO DO ! when > 7.01 */
    break;

  case 0x28:			/* Set video mode */
    mouse_setvidmode();		/* TO DO ! now ! */
    break;

  case 0x29:			/* Count video modes */
    mouse_countvids();		/* TO DO ! now ! */
    break;

  case 0x2A:			/* Get cursor hotspot */
    mouse_gethotspot();		/* TO DO ! when > 7.02 */
    break;

  case 0x2B:			/* Set acceleration curves */
    mouse_setacccurv();		/* TO DO ! now */
    break;

  case 0x2C:			/* Read acceleration curves */
    mouse_getacccurv();		/* TO DO ! now */
    break;

  case 0x2D:			/* Set/Get active acceleration curves */
    mouse_setgetactcurv();	/* TO DO ! now */
    break;

  case 0x2E:			/* Set acceleration profile names */
    mouse_setaccprofile();	/* TO DO ! when > 8.01 */
    break;

  case 0x2F:			/* Hardware reset */
    mouse_hardreset();		/* TO DO ! when > 7.02 */
    break;

  case 0x30:			/* Set/Get ballpoint information */
    mouse_getsetballpoint();	/* TO DO ! when > 7.04 */
    break;
#endif

  case 0x31:			/* Get minimum/maximum virtual coords */
    mouse_getmaxminvirt();	/* TO DO ! when > 7.05 */
    break;

#if 0
  case 0x32:			/* Get active advanced functions */
    mouse_getadvfunc();		/* TO DO ! when > 7.05 */
    break;

  case 0x33:			/* Get switch settings */
    mouse_getswitchset();	/* TO DO ! when > 7.05 */
    break;

  case 0x34:			/* Get MOUSE.INI ! Location */
    mouse_getinilocation();	/* TO DO ! when > 8.00 */
    break;

  case 0x35:			/* LCD Large Screen support */
    mouse_lcd();		/* TO DO ! when > 8.10 */
    break;
#endif

/*
 * Functions after 0x35 are not Microsoft calls, Implemented for PC Mouse
 * and Mouse Systems 3 button emulation
 */

  case 0x42:
    mouse_getstorereq();	/* Get storage requirements */
    break;

  default:
    m_printf("MOUSE: function 0x%04x not implemented\n", LWORD(eax));
    break;
  }

  if (mouse.cursor_on == 0)
     mouse_update_cursor();

  return 1;
}

void
mouse_detpage(void)
{
  LWORD(ebx) = mouse.display_page;
}

void
mouse_disppage(void)
{
  /* turn off (ie erase) the mouse cursor before changing the
  	display page so that it doesn't erase the wrong page */
  mouse_cursor(-1);
  mouse.display_page = LWORD(ebx);
  mouse_cursor(1);
}

void
mouse_hardintrate(void)
{
  /* Only used by the Inport Mouse */
}

void
mouse_detalternate(void)
{
  error("MOUSE: RETURN ALTERNATE MOUSE USER HANDLER unimplemented.\n");
  LWORD(ecx) = 0;			/* Error ! */
}

void
mouse_alternate(void)
{
  error("MOUSE: SET ALTERNATE MOUSE USER HANDLER unimplemented.\n");
  LWORD(eax) = 0xffff;			/* Failed to install alternate */
}

void
mouse_doublespeed(void)
{
  mouse.threshold = LWORD(edx);		/* Double speed at mickeys per second */
}

void
mouse_largecursor(void)
{
  LWORD(eax) = 0;			/* Failed to set large cursor */
}

static void
mouse_exclusionarea(void)
{
  mouse.exc_ux = LWORD(ecx);
  mouse.exc_uy = LWORD(edx);
  mouse.exc_lx = LWORD(esi);
  mouse.exc_ly = LWORD(edi);
}

static void
mouse_getstorereq(void)
{
  if (!mouse.threebuttons)
    LWORD(eax) = 0x42;                /* Configures mouse for Microsoft Mode */
  else {
    LWORD(eax) = 0xffff;              /* Configures mouse for PC Mouse Mode */
    LWORD(ebx) = sizeof(mouse);       /* applies to Genius Mouse too */
  }
}

static void
mouse_getmaxcoord(void)
{
  LWORD(ebx) = (mice->intdrv ? 1 : 0);
  LWORD(ecx) = mouse.maxx;
  LWORD(edx) = mouse.maxy;
  m_printf("MOUSE: COORDINATES: x: %d, y: %d\n",mouse.maxx,mouse.maxy);
}

static void
mouse_getmaxminvirt(void)
{
  LWORD(eax) = mouse.virtual_minx;
  LWORD(ebx) = mouse.virtual_miny;
  LWORD(ecx) = mouse.virtual_maxx;
  LWORD(edx) = mouse.virtual_maxy;
  m_printf("MOUSE: VIRTUAL COORDINATES: x: %d-%d, y: %d-%d\n",
    mouse.virtual_minx, mouse.virtual_maxx,
    mouse.virtual_miny, mouse.virtual_maxy);
}

static void
mouse_getgeninfo(void)
{
  /* Set AX to 0 */
  LWORD(eax) = 0x0000;

  /* Bits 0-7 are count of currently-active Mouse Display Drivers (MDD) */
  LWORD(eax) |= 0x0000;			/* For now none ! */

  /* Bits 8-11 are the interrupt rate */
  LWORD(eax) |= 0x0000;			/* Don't allow interrupts */

  /* Bits 12-13 are Mouse cursor information */
  if (mouse.gfx_cursor)
    LWORD(eax) |= 0x2000;		/* Set graphic cursor */
  else
    LWORD(eax) |= 0x0000;		/* Set software text cursor */

  /* Bit 14 sets driver is newer integrated type */
  LWORD(eax) |= 0x0000;			/* Not the newer integrated type */

  /* Bit 15, 0=COM file, 1=SYS file */
  LWORD(eax) |= 0x8000;			/* Set SYS file */
}

static void
mouse_software_reset(void)
{
  m_printf("MOUSE: software reset on mouse\n");

  /* Disable cursor, and de-install current event handler */
  mouse.cs = 0;
  mouse.ip = 0;
  mouse.ps2.cs = 0;
  mouse.ps2.ip = 0;
  mouse_enable_internaldriver();
  if (mouse.cursor_on < -1) {
    m_printf("MOUSE: normalizing hide count, %i\n", mouse.cursor_on);
    mouse.cursor_on = -1;
  }

  /* Return 0xffff on success, 0x21 on failure */
  LWORD(eax) = 0xffff;

  /* Return Number of mouse buttons */
  if (mouse.threebuttons)
    LWORD(ebx) = 3;
  else
    LWORD(ebx) = 2;
}

static void
mouse_detsensitivity(void)
{
  LWORD(ebx) = mouse.sens_x;         /* horizontal speed */
  LWORD(ecx) = mouse.sens_y;         /* vertical speed */
  LWORD(edx) = mouse.threshold;       /* double speed threshold */
}

static void
mouse_setsensitivity(void)
{
  mouse.sens_x = LWORD(ebx);
  if (mouse.sens_x == 0)
    mouse.sens_x++;
  mouse.sens_y = LWORD(ecx);
  if (mouse.sens_y == 0)
    mouse.sens_y++;
  mouse.threshold = LWORD(edx);
  m_printf("MOUSE: set sensitivity %i:%i %i\n", LWORD(ebx), LWORD(ecx),
      LWORD(edx));
}

static void
mouse_restorestate(void)
{
  int current_state;
  current_state = mouse.cursor_on;
  /* turn cursor off before restore */
  if (current_state >= 0) {
	mouse.cursor_on = 0;
  	mouse_cursor(-1);
  }

  memcpy((void *)&mouse, MK_FP32(SREG(es),LWORD(edx)), sizeof(mouse));

  /* regenerate mouse graphics cursor from masks; they take less
  	space than the "compiled" version. */
  define_graphics_cursor(mouse.graphscreenmask, mouse.graphcursormask);

  /* we turned off the mouse cursor prior to saving, so turn it
  	back on again at the restore. */
  if (mouse.cursor_on >= 0) {
	mouse.cursor_on = -1;
  	mouse_cursor(1);
  }

  m_printf("MOUSE: Restore mouse state\n");
}


static void
mouse_storestate(void)
{
  int current_state;
  current_state = mouse.cursor_on;
  /* always turn off mouse cursor prior to saving, that way we don't
  	have to remember the erase information */
  if (current_state >= 0) {
	mouse.cursor_on = 0;
  	mouse_cursor(-1);
  }
  mouse.cursor_on = current_state;

  memcpy(MK_FP32(SREG(es),LWORD(edx)), (void *)&mouse, sizeof(mouse));

  /* now turn it back on */
  if (mouse.cursor_on >= 0) {
	mouse.cursor_on = -1;
  	mouse_cursor(1);
  }
  mouse.cursor_on = current_state;
  m_printf("MOUSE: Save mouse state\n");
}


static void
mouse_detstatbuf(void)
{
  LWORD(ebx) = sizeof(mouse);
  m_printf("MOUSE: get size of state buffer 0x%04x\n", LWORD(ebx));
}

static void
mouse_excevhand(void)
{
  unsigned short tmp1, tmp2, tmp3;

  m_printf("MOUSE: exchange subroutines\n");
  tmp1 = mouse.mask;
  tmp2 = mouse.cs;
  tmp3 = mouse.ip;
  mouse_setsub();
  LWORD(ecx) = tmp1;
  SREG(es) = tmp2;
  LWORD(edx) = tmp3;
}

void
mouse_setcurspeed(void)
{
  long long oldx, oldy, newx, newy;

  if (mouse.cursor_on < 0) {
    /* when speed changes, in ungrabbed mode we need to update deltas
     * so that the (invisible) cursor not to move */
    scale_coords(mouse.px_abs, mouse.py_abs, mouse.px_range, mouse.py_range,
	    &oldx, &oldy);
    scale_coords3(mouse.px_abs, mouse.py_abs, mouse.px_range, mouse.py_range,
	    LWORD(ecx), LWORD(edx), &newx, &newy);
    mouse.x_delta -= newx - oldx;
    mouse.y_delta -= newy - oldy;
  }

  m_printf("MOUSE: set cursor speed: x=%i, y=%i\n", LWORD(ecx), LWORD(edx));
  if (LWORD(ecx) > 0)
    mouse.speed_x = LWORD(ecx);
  if (LWORD(edx) > 0)
    mouse.speed_y = LWORD(edx);
}

static int get_unsc_x(int dx)
{
	return dx * mouse.px_range;
}

static int get_unsc_y(int dy)
{
	return dy * mouse.py_range;
}

static void setxy(int x, int y)
{
	mouse.unsc_x = get_unsc_x(x);
	mouse.unsc_y = get_unsc_y(y);
}

/* change pos from software preserving coord reminders */
static void setxy_sw(int x, int y)
{
	int rem_x = mouse.unsc_x % get_unsc_x(1 << mouse.xshift);
	int rem_y = mouse.unsc_y % get_unsc_y(1 << mouse.yshift);

	mouse.unsc_x = get_unsc_x(mouse_roundx(x)) + rem_x;
	mouse.unsc_y = get_unsc_y(mouse_roundy(y)) + rem_y;
}

static int get_unsc_mk_x(int dx)
{
	return dx * mouse.px_range;
}

static int get_unsc_mk_y(int dy)
{
	return dy * mouse.py_range;
}

static void setmxy(int x, int y)
{
	mouse.unscm_x = get_unsc_mk_x(x);
	mouse.unscm_y = get_unsc_mk_y(y);
}

static void add_abs_coords(long long udx, long long udy)
{
	mouse.unsc_x += udx;
	mouse.unsc_y += udy;

	mouse_clip_coords();
}

static void add_mickey_coords(long long udx, long long udy)
{
	mouse.unscm_x += udx;
	mouse.unscm_y += udy;
}

static void get_scale_range(int *mx_range, int *my_range)
{
	if (config.mouse.ignore_speed) {
		*mx_range = _max(mouse.maxx, mouse.virtual_maxx) - MOUSE_MINX + 1;
		*my_range = _max(mouse.maxy, mouse.virtual_maxy) - MOUSE_MINY + 1;
	} else {
		*mx_range = mouse.maxx - MOUSE_MINX + 1;
		*my_range = mouse.maxy - MOUSE_MINY + 1;
	}
}

static void add_mk(int dx, int dy)
{
	int mx_range, my_range;
	long long udx, udy;

	/* pixel range set to 1 */
	udx = dx * 8;
	udy = dy * 8;
	get_scale_range(&mx_range, &my_range);

	add_mickey_coords(udx, udy);
	add_abs_coords(udx * mx_range / (mouse.speed_x * mouse.min_max_x),
		    udy * my_range / (mouse.speed_y * mouse.min_max_y));
}

static void add_px(int dx, int dy)
{
	long long mdx, mdy, udx, udy;

	scale_coords_spd_unsc(dx, dy, &mdx, &mdy);
	scale_coords_spd_unsc_mk(dx, dy, &udx, &udy);
	add_mickey_coords(udx, udy);
	add_abs_coords(mdx, mdy);
}

static void reset_scale(void)
{
  /* To get quicken to run correctly I no longer ignore maxx and maxy
   * values as stored in the BIOS variables for text mode.
   * Does this violate a spec?
   * -- EB 25 May 1998
   */

/* DANG_BEGIN_REMARK
 * Whoever wrote the dos mouse driver spec was brain dead...
 * For some video modes the mouse driver appears to randomly
 * pick a shift factor, possibly to keep at least a 640x200 resolution.
 *
 * The general programming documentation doesn't make this clear.
 * And says that in text modes it is safe to divide the resolution by
 * 8 to get the coordinates in characters.
 *
 * The only safe way to handle the mouse driver is to call function
 * 0x26 Get max x & max y coordinates and scale whatever the driver
 * returns yourself.
 *
 * To handle programs written by programmers who weren't so cautious a
 * doctrine of least surprise has been implemented.
 *
 * As much as possible do the same as a standard dos mouse driver in the
 * original vga modes 0,1,2,3,4,5,6,7,13,14,15,16,17,18,19.
 *
 * For other text modes allow the divide by 8 technique to work.
 * For other graphics modes return x & y in screen coordinates.
 * Except when those modes are either 40x?? or 320x???
 * and then handle the x resolution as in 40x25 and 320x200 modes.
 *
 * 320x200 modes are slightly controversial as I have indications that
 * not all mouse drivers do the same thing. So I have taken the
 * simplest, and most common route, which is also long standing dosemu
 * practice of always shifting the xaxis by 1.  When I researched this
 * I could find no examples that did otherwise.
 *
 *
 * -- Eric Biederman 19 August 1998
 *
 * DANG_END_REMARK
 */

  if (mouse_current_video.textgraph == 'T') {
    mouse.xshift = 3;
    mouse.yshift = 3;
    if (mouse_current_video.width == 40) {
	    mouse.xshift = 4;
    }
    mouse.gfx_cursor = FALSE;
  } else {
    mouse.gfx_cursor = TRUE;
    mouse.xshift = 0;
    mouse.yshift = 0;
    while ((mouse_current_video.width << mouse.xshift) < mouse.min_max_x) {
      mouse.xshift++;
    }
    while ((mouse_current_video.height << mouse.yshift) < mouse.min_max_y) {
      mouse.yshift++;
    }
    define_graphics_cursor(default_graphscreenmask,default_graphcursormask);
  }

  /* Set the maximum sizes */
  mouse.maxx = mouse_current_video.width << mouse.xshift;
  mouse.maxy = mouse_current_video.height << mouse.yshift;

  /* Change mouse.maxx & mouse.maxy from ranges to maximums */
  mouse.maxx = mouse_roundx(mouse.maxx - 1);
  mouse.maxy = mouse_roundy(mouse.maxy - 1);
  mouse.maxx += (1 << mouse.xshift) -1;
  mouse.maxy += (1 << mouse.yshift) -1;
}

static void
mouse_reset_to_current_video_mode(void)
{
  int err;
  /* This looks like a generally safer place to reset scaling factors
   * then in mouse_reset, as it gets called more often.
   * -- Eric Biederman 29 May 2000
   */
  mouse.speed_x = INIT_SPEED_X;
  mouse.speed_y = INIT_SPEED_Y;
  mouse.sens_x = 100;
  mouse.sens_y = 100;
  mouse.threshold = 200;
  mouse.px_abs = 0;
  mouse.py_abs = 0;

 /*
  * Here we make sure text modes are resolved properly, according to the
  * standard vga/ega/cga/mda specs for int10. If we don't know that we are
  * in text mode, then we return pixel resolution and assume graphic mode.
  * stsp: use mode 6 as a fall-back.
  */
  err = get_current_video_mode(&mouse_current_video);
  if (err) {
    m_printf("MOUSE: fall-back to mode 6\n");
    vidmouse_set_video_mode(-1);
    vidmouse_get_video_mode(6, &mouse_current_video);
  }

  if (!mouse.win31_mode)
    reset_scale();

  /* force update first time after reset */
  mouse.oldrx = -1;

  /* Setup up the virtual coordinates */
  mouse.virtual_minx = MOUSE_MINX;
  mouse.virtual_maxx = mouse.maxx;
  mouse.virtual_miny = MOUSE_MINY;
  mouse.virtual_maxy = mouse.maxy;

  /* counters may become out-of-range, adjust */
  mouse_clip_coords();

  m_printf("maxx=%i, maxy=%i speed_x=%i speed_y=%i type=%d\n",
	   mouse.maxx, mouse.maxy, mouse.speed_x, mouse.speed_y,
	   mice->type);
}

static void int33_mouse_enable_native_cursor(int flag, void *udata)
{
  mice->native_cursor = flag;
  mouse_do_cur(1);
}

static void scale_coords_spd(int x, int y, int x_range, int y_range,
	int mx_range, int my_range, int speed_x, int speed_y,
	long long *s_x, long long *s_y)
{
	*s_x = ((long long)x * mx_range * mice->init_speed_x * 100) /
	    (x_range * speed_x * mouse.sens_x) + MOUSE_MINX;
	*s_y = ((long long)y * my_range * mice->init_speed_y * 100) /
	    (y_range * speed_y * mouse.sens_y) + MOUSE_MINY;
}

static void scale_coords_spd_unsc_mk(int x, int y, long long *s_x,
        long long *s_y)
{
	scale_coords_spd(x, y, 1, 1, mouse.min_max_x, mouse.min_max_y,
		    1, 1, s_x, s_y);
}

static void scale_coords_spd_unsc(int x, int y, long long *s_x, long long *s_y)
{
	int mx_range, my_range;

	get_scale_range(&mx_range, &my_range);
	scale_coords_spd(x, y, 1, 1, mx_range, my_range,
			mouse.speed_x, mouse.speed_y, s_x, s_y);
}

static void scale_coords_basic(int x, int y, int x_range, int y_range,
	int mx_range, int my_range, long long *s_x, long long *s_y)
{
	*s_x = ((long long)x * mx_range) / x_range + MOUSE_MINX;
	*s_y = ((long long)y * my_range) / y_range + MOUSE_MINY;
}

static void scale_coords2(int x, int y, int x_range, int y_range,
	int mx_range, int my_range, int speed_x, int speed_y,
	long long *s_x, long long *s_y)
{
	/* if cursor is not visible we need to take into
	 * account the user's speed. If it is visible, then
	 * in ungrabbed mode we have to ignore user's speed. */
	if (!config.mouse.ignore_speed && mouse.cursor_on < 0)
		scale_coords_spd(x, y, x_range, y_range,
			mx_range, my_range, speed_x, speed_y,
			s_x, s_y);
	else
		scale_coords_basic(x, y, x_range, y_range,
			mx_range, my_range, s_x, s_y);
}

static void scale_coords3(int x, int y, int x_range, int y_range,
	int speed_x, int speed_y, long long *s_x, long long *s_y)
{
	int mx_range, my_range;

	get_scale_range(&mx_range, &my_range);
	scale_coords2(x, y, x_range, y_range, mx_range, my_range,
			_min(speed_x, mice->init_speed_x),
			_min(speed_y, mice->init_speed_y), s_x, s_y);
}

static void scale_coords(int x, int y, int x_range, int y_range,
	long long *s_x, long long *s_y)
{
	int mx_range, my_range;

	get_scale_range(&mx_range, &my_range);
	scale_coords2(x, y, x_range, y_range, mx_range, my_range,
		/* simcity hack: only handle cursor speed-ups but ignore
		 * slow-downs. */
		_min(mouse.speed_x, mice->init_speed_x),
		_min(mouse.speed_y, mice->init_speed_y), s_x, s_y);
}

static void mouse_reset(void)
{
  m_printf("MOUSE: reset mouse/installed!\n");

  mouse.ps2.state = 0;
  mouse.ps2.cs = 0;
  mouse.ps2.ip = 0;

  mouse_enable_internaldriver();

  /* Return 0xffff on mouse installed, 0x0000 - no mouse driver installed */
  /* Return number of mouse buttons */
  LWORD(eax) = 0xffff;
  if (mouse.threebuttons)
      LWORD(ebx)=3;
  else
      LWORD(ebx)=2;
  mouse_reset_to_current_video_mode();

  /* turn cursor off if reset requested by software and it was on. */
  if (mouse.cursor_on >= 0) {
  	mouse.cursor_on = 0;
  	mouse_cursor(-1);
  }

  mouse.cursor_on = -1;
  mouse.lbutton = mouse.mbutton = mouse.rbutton = 0;
  mouse.lpcount = mouse.mpcount = mouse.rpcount = 0;
  mouse.lrcount = mouse.mrcount = mouse.rrcount = 0;
  mouse.wmcount = 0;

  mouse.exc_lx = mouse.exc_ux = -1;
  mouse.exc_ly = mouse.exc_uy = -1;

  mouse.ip = mouse.cs = 0;
  mouse.mask = 0;		/* no interrupts */

  mouse.threshold = 64;

  mouse.lpx = mouse.lpy = mouse.lrx = mouse.lry = 0;
  mouse.rpx = mouse.rpy = mouse.rrx = mouse.rry = 0;

  mouse.unscm_x = mouse.unscm_y = 0;
  mouse.unsc_x = mouse.unsc_y = 0;
  mouse.x_delta = mouse.y_delta = 0;
  mouse.need_resync = 0;
  dragged.cnt = 0;

  mouse.textscreenmask = 0xffff;
  mouse.textcursormask = 0x7f00;
  memcpy((void *)mouse.graphscreenmask,default_graphscreenmask,32);
  memcpy((void *)mouse.graphcursormask,default_graphcursormask,32);
  mouse.hotx = mouse.hoty = -1;

  mouse.win31_mode = 0;

  mouse_do_cur(1);
}

void
mouse_cursor(int flag)	/* 1=show, -1=hide */
{
  int need_resync = 0;
  /* Delete exclusion zone, if show cursor applied */
  if (flag == 1) {
    mouse.exc_lx = mouse.exc_ux = -1;
    mouse.exc_ly = mouse.exc_uy = -1;
    if (mouse.x_delta || mouse.y_delta) {
      mouse.x_delta = mouse.y_delta = 0;
      need_resync = 1;
    }
  }

  /* already on, don't do anything */
  if (flag == 1 && mouse.cursor_on == 0)
  	return;

  /* adjust hide count (always negative or zero) */
  mouse.cursor_on += flag;

  /* update the cursor if we just turned it off or on */
  if ((flag == -1 && mouse.cursor_on == -1) ||
  		(flag == 1 && mouse.cursor_on == 0)){
	  mouse_do_cur(1);
    if (flag == 1 && need_resync && !dragged.cnt)
      do_move_abs(mouse.px_abs, mouse.py_abs, mouse.px_range, mouse.py_range,
          mouse.cursor_on >= 0);
    /* first time enabling should work immediately */
    if (flag == 1 && !mouse.visibility_changed)
      mouse_client_show_cursor(mouse.visibility_locked ?: mouse.cursor_on >= 0);
    else
      mouse.visibility_changed++;
  }

  m_printf("MOUSE: %s mouse cursor %d\n", flag > 0 ? "show" : "hide", mouse.cursor_on);
}

void
mouse_pos(void)
{
  m_printf("MOUSE: get mouse position x:%d, y:%d, b(l%d m%d r%d)\n", get_mx(),
	   get_my(), mouse.lbutton, mouse.mbutton, mouse.rbutton);
  LWORD(ecx) = MOUSE_RX;
  LWORD(edx) = MOUSE_RY;
  LO(bx) = (mouse.rbutton ? 2 : 0) | (mouse.lbutton ? 1 : 0);
  if (mouse.threebuttons)
     LO(bx) |= (mouse.mbutton ? 4 : 0);
  HI(bx) = mouse.wmcount;
  mouse.wmcount = 0;
}

/* Set mouse position */
void
mouse_setpos(void)
{
  setxy_sw(LWORD(ecx), LWORD(edx));
  mouse_clip_coords();
  mouse_hide_on_exclusion();
  if (mouse.cursor_on >= 0) {
    mouse.x_delta = mouse.y_delta = 0;
    mouse_do_cur(1);
  } else {
    long long abs_x, abs_y;
    scale_coords(mouse.px_abs, mouse.py_abs, mouse.px_range, mouse.py_range,
	    &abs_x, &abs_y);
    mouse.x_delta = get_mx() - abs_x;
    mouse.y_delta = get_my() - abs_y;
  }
  m_printf("MOUSE: set cursor pos x:%d, y:%d delta x:%d, y:%d\n",
	get_mx(), get_my(), mouse.x_delta, mouse.y_delta);
}

void
mouse_bpressinfo(void)
{
  m_printf("MOUSE: Button press info\n");

  if (LWORD(ebx) > 2)
    LWORD(ebx) = 0;

  switch(LWORD(ebx)) {

  case 0xffff:				/* wheel movement */
    LWORD(ebx) = mouse.wmcount;
    mouse.wmcount = 0;
    LWORD(ecx) = mouse.wmx;
    LWORD(edx) = mouse.wmy;
    break;

  case 0:				/* left button */
    LWORD(ebx) = mouse.lpcount;
    mouse.lpcount = 0;
    LWORD(ecx) = mouse.lpx;
    LWORD(edx) = mouse.lpy;
    break;

  case 1:				/* right button */
    LWORD(ebx) = mouse.rpcount;
    mouse.rpcount = 0;
    LWORD(ecx) = mouse.rpx;
    LWORD(edx) = mouse.rpy;
    break;

  case 2:				/* middle button */
    LWORD(ebx) = mouse.mpcount;
    mouse.mpcount = 0;
    LWORD(ecx) = mouse.mpx;
    LWORD(edx) = mouse.mpy;
    break;
  }

  LO(ax) = (mouse.rbutton ? 2 : 0) | (mouse.lbutton ? 1 : 0);
  if (mouse.threebuttons)
     LO(ax) |= (mouse.mbutton ? 4 : 0);
  HI(ax) = mouse.wmcount;
}

void
mouse_brelinfo(void)
{
  m_printf("MOUSE: Button release info\n");

  if (LWORD(ebx) > 2)
    LWORD(ebx) = 0;

  switch(LWORD(ebx)) {

  case 0xffff:				/* wheel movement */
    LWORD(ebx) = mouse.wmcount;
    mouse.wmcount = 0;
    LWORD(ecx) = mouse.wmx;
    LWORD(edx) = mouse.wmy;
    break;

  case 0:				/* left button */
    LWORD(ebx) = mouse.lrcount;
    mouse.lrcount = 0;
    LWORD(ecx) = mouse.lrx;
    LWORD(edx) = mouse.lry;
    break;

  case 1:				/* right button */
    LWORD(ebx) = mouse.rrcount;
    mouse.rrcount = 0;
    LWORD(ecx) = mouse.rrx;
    LWORD(edx) = mouse.rry;
    break;

  case 2:				/* middle button */
    LWORD(ebx) = mouse.mrcount;
    mouse.mrcount = 0;
    LWORD(ecx) = mouse.mrx;
    LWORD(edx) = mouse.mry;
    break;
  }

  LO(ax) = (mouse.rbutton ? 2 : 0) | (mouse.lbutton ? 1 : 0);
  if (mouse.threebuttons)
     LO(ax) |= (mouse.mbutton ? 4 : 0);
  HI(ax) = mouse.wmcount;
}

void
mouse_setxminmax(void)
{
  m_printf("MOUSE: set horz. min: %d, max: %d\n", LWORD(ecx), LWORD(edx));

  /* if the min > max, they are swapped */
  mouse.virtual_minx = (LWORD(ecx) > LWORD(edx)) ? LWORD(edx) : LWORD(ecx);
  mouse.virtual_maxx = (LWORD(ecx) > LWORD(edx)) ? LWORD(ecx) : LWORD(edx);
  mouse.virtual_minx = mouse_roundx(mouse.virtual_minx);
  mouse.virtual_maxx = mouse_roundx(mouse.virtual_maxx);
  mouse.virtual_maxx += (1 << mouse.xshift) -1;

  mouse_clip_coords();
}

void
mouse_setyminmax(void)
{
  m_printf("MOUSE: set vert. min: %d, max: %d\n", LWORD(ecx), LWORD(edx));

  /* if the min > max, they are swapped */
  mouse.virtual_miny = (LWORD(ecx) > LWORD(edx)) ? LWORD(edx) : LWORD(ecx);
  mouse.virtual_maxy = (LWORD(ecx) > LWORD(edx)) ? LWORD(ecx) : LWORD(edx);
  mouse.virtual_miny = mouse_roundy(mouse.virtual_miny);
  mouse.virtual_maxy = mouse_roundy(mouse.virtual_maxy);
  mouse.virtual_maxy += (1 << mouse.yshift) -1;

  mouse_clip_coords();
}

void
mouse_set_gcur(void)
{
  unsigned short *ptr = MK_FP32(SREG(es),LWORD(edx));

  m_printf("MOUSE: set gfx cursor...hspot: %d, vspot: %d, masks: %04x:%04x\n",
	   LWORD(ebx), LWORD(ecx), SREG(es), LWORD(edx));

  /* remember hotspot location */
  mouse.hotx = LWORD(ebx);
  mouse.hoty = LWORD(ecx);

  /* remember mask definition */
  memcpy((void *)mouse.graphscreenmask,ptr,32);
  memcpy((void *)mouse.graphcursormask,ptr+16,32);

  /* compile it so that it can actually be drawn. */
  define_graphics_cursor(mouse.graphscreenmask, mouse.graphcursormask);
}

void
mouse_set_tcur(void)
{
  m_printf("MOUSE: set text cursor...type: %d, start: 0x%04x, end: 0x%04x\n",
	   LWORD(ebx), LWORD(ecx), LWORD(edx));

  if (LWORD(ebx)==0) {				/* Software cursor */
	  mouse.textscreenmask = LWORD(ecx);
	  mouse.textcursormask = LWORD(edx);
	  mouse_do_cur(1);
  } else {					/* Hardware cursor */
  /* CX - should be starting line of hardware cursor
   * DX - should be ending line of hardware cursor
   * FIXME ! -- then again, who ever actually uses the hw cursor? */
   /* (the only advantage of the hw cursor is that you don't have to
   	erase it before hitting vram */
	  mouse.textscreenmask = 0x7fff;
	  mouse.textcursormask = 0xff00;
	  mouse_do_cur(1);
  }
}

void
mouse_setsub(void)
{
  mouse.cs = SREG(es);
  mouse.ip = LWORD(edx);
  mouse.mask = LWORD(ecx);

  m_printf("MOUSE: user defined sub %04x:%04x, mask 0x%02x\n",
	   SREG(es), LWORD(edx), LWORD(ecx));
}

void
mouse_mickeys(void)
{
  int mkx = mickeyx();
  int mky = mickeyy();
  m_printf("MOUSE: read mickeys %d %d\n", mkx, mky);
  LWORD(ecx) = mkx;
  LWORD(edx) = mky;

  /* counters get reset after read */
  if (mkx)
    mouse.unscm_x -= get_unsc_mk_x(mkx) * 8;
  if (mky)
    mouse.unscm_y -= get_unsc_mk_y(mky) * 8;

  if (dragged.cnt) {
    dragged.cnt = 0;
    if (dragged.skipped) {
      dragged.skipped = 0;
      do_move_abs(dragged.x, dragged.y, dragged.x_range, dragged.y_range,
          mouse.cursor_on >= 0);
    }
  }
}

void
mouse_version(void)
{
  LWORD(ebx) = MOUSE_VERSION;
  if (mice->com == -1) {
    HI(cx) = 4;			/* ps2 mouse */
    LO(cx) = 0;
  } else {
    HI(cx) = 2;			/* serial mouse */
    LO(cx) = com_cfg[mice->com].irq;
  }
  m_printf("MOUSE: get version %04x, 0x%04x\n", LWORD(ebx), LWORD(ecx));
}

void
mouse_disable_internaldriver(void)
{
  LWORD(eax) = 0x001F;
  SREG(es) = mouse.cs;
  LWORD(ebx) = mouse.ip;

  mouse.enabled = FALSE;

  mouse_client_show_cursor(1);
  m_printf("MOUSE: Disable InternalDriver\n");
}

void
mouse_enable_internaldriver(void)
{
  mouse.enabled = TRUE;
  mouse_client_show_cursor(mouse.cursor_on >= 0);
  m_printf("MOUSE: Enable InternalDriver\n");
}

/* Keyboard mouse control :)  */
void mouse_keyboard(bool make, t_keysym key)
{
	static struct keyboard_mouse_state {
		int l, r, u, d;
		int ul, ur, dl, dr;
		int lbutton, mbutton, rbutton;
	} state;
	int dx, dy;
	switch (key) {
	case DKY_MOUSE_DOWN:
		state.d = !!make;
		break;
	case DKY_MOUSE_LEFT:
		state.l = !!make;
		break;
	case DKY_MOUSE_RIGHT:
		state.r = !!make;
		break;
	case DKY_MOUSE_UP:
		state.u = !!make;
		break;
	case DKY_MOUSE_UP_AND_LEFT:
		state.ul = make;
		break;
	case DKY_MOUSE_UP_AND_RIGHT:
		state.ur = make;
		break;
	case DKY_MOUSE_DOWN_AND_LEFT:
		state.dl = make;
		break;
	case DKY_MOUSE_DOWN_AND_RIGHT:
		state.dr = make;
		break;
	case DKY_MOUSE_BUTTON_LEFT:
		state.lbutton = make;
		break;
	case DKY_MOUSE_BUTTON_RIGHT:
		state.rbutton = make;
		break;
	case DKY_MOUSE_BUTTON_MIDDLE:
		state.mbutton = make;
		break;
	default:
		m_printf("MOUSE: keyboard_mouse(), key 0x%02x unknown!\n", key);
		break;
	}
	dx = dy = 0;
	if (state.d) {
		dy += 1;
	}
	if (state.dl) {
		dy += 1;
		dx -= 1;
	}
	if (state.dr) {
		dy += 1;
		dx += 1;
	}
	if (state.u) {
		dy -= 1;
	}
	if (state.ul) {
		dy -= 1;
		dx -= 1;
	}
	if (state.ur) {
		dy -= 1;
		dx += 1;
	}
	if (state.r) {
		dx += 1;
	}
	if (state.l) {
		dx -= 1;
	}
	mouse_move_mickeys(dx, dy, MOUSE_VKBD);
	mouse_move_buttons(state.lbutton, state.mbutton, state.rbutton,
		MOUSE_VKBD);
}

static int mouse_clip_coords2(int x, int y, int *r_x, int *r_y)
{
	int clipped = 0;

	*r_x = x;
	*r_y = y;
	/* put the mouse coordinate in bounds */
	if (x < mouse.virtual_minx) {
		*r_x = mouse.virtual_minx;
		clipped |= 1;
	}
	if (y < mouse.virtual_miny) {
		*r_y = mouse.virtual_miny;
		clipped |= 2;
	}
	if (x > mouse.virtual_maxx) {
		*r_x = mouse.virtual_maxx;
		clipped |= 1;
	}
	if (y > mouse.virtual_maxy) {
		*r_y = mouse.virtual_maxy;
		clipped |= 2;
	}
	return clipped;
}

static int mouse_clip_coords(void)
{
	int newx, newy, ret;

	ret = mouse_clip_coords2(_get_mx(), _get_my(), &newx, &newy);
	if (ret & 1)
	    mouse.unsc_x = get_unsc_x(newx);
	if (ret & 2)
	    mouse.unsc_y = get_unsc_y(newy);
	return ret;
}

static void mouse_hide_on_exclusion(void)
{
  /* Check exclusion zone, then turn off cursor if necessary */
  if (mouse.exc_ux != -1) {
    short xl, xr, yt, yb;

    /* find the cursor box's edges coords (in mouse coords not screen) */
    if (mouse.gfx_cursor) { /* graphics cursor */
      xl = get_mx() - (mouse.hotx << mouse.xshift);
      xr = get_mx() + ((HEIGHT-1 - mouse.hotx) << mouse.xshift);
      yt = get_my() - (mouse.hoty << mouse.yshift);
      yb = get_my() + ((HEIGHT-1 - mouse.hoty) << mouse.yshift);
    }
    else { /* text cursor */
      xl = MOUSE_RX;
      xr = MOUSE_RX + (1 << mouse.xshift) - 1;
      yt = MOUSE_RY;
      yb = MOUSE_RY + (1 << mouse.yshift) - 1;

    }
    /* check zone boundary */
    if ((xr >= mouse.exc_ux) &&
        (xl <= mouse.exc_lx) &&
        (yb >= mouse.exc_uy) &&
        (yt <= mouse.exc_ly))
	mouse_cursor(-1);
  }
}

static void mouse_move(int abs)
{
  /* delayed visibility change */
  if (mouse.visibility_changed) {
    mouse.visibility_changed = 0;
    mouse_client_show_cursor(mouse.visibility_locked ?: mouse.cursor_on >= 0);
  }

  mouse_hide_on_exclusion();
  mouse_update_cursor();

  m_printf("MOUSE: move: x=%d,y=%d\n", get_mx(), get_my());

  mouse_delta(DELTA_CURSOR | (abs ? DELTA_ABSOLUTE : 0));
}

static void mouse_lb(void)
{
  m_printf("MOUSE: left button %s\n", mouse.lbutton ? "pressed" : "released");
  if (!mouse.lbutton) {
    mouse.lrcount++;
    mouse.lrx = MOUSE_RX;
    mouse.lry = MOUSE_RY;
    mouse_delta(DELTA_LEFTBUP);
  }
  else {
    mouse.lpcount++;
    mouse.lpx = MOUSE_RX;
    mouse.lpy = MOUSE_RY;
    mouse_delta(DELTA_LEFTBDOWN);
  }
}

static void mouse_mb(void)
{
  m_printf("MOUSE: middle button %s\n", mouse.mbutton ? "pressed" : "released");
  if (!mouse.mbutton) {
    mouse.mrcount++;
    mouse.mrx = MOUSE_RX;
    mouse.mry = MOUSE_RY;
    mouse_delta(DELTA_MIDDLEBUP);
  }
  else {
    mouse.mpcount++;
    mouse.mpx = MOUSE_RX;
    mouse.mpy = MOUSE_RY;
    mouse_delta(DELTA_MIDDLEBDOWN);
  }
}

static void mouse_rb(void)
{
  m_printf("MOUSE: right button %s\n", mouse.rbutton ? "pressed" : "released");
  if (!mouse.rbutton) {
    mouse.rrcount++;
    mouse.rrx = MOUSE_RX;
    mouse.rry = MOUSE_RY;
    mouse_delta(DELTA_RIGHTBUP);
  }
  else {
    mouse.rpcount++;
    mouse.rpx = MOUSE_RX;
    mouse.rpy = MOUSE_RY;
    mouse_delta(DELTA_RIGHTBDOWN);
  }
}

static void int33_mouse_move_buttons(int lbutton, int mbutton, int rbutton, void *udata)
{
	if (dragged.skipped) {
		dragged.skipped = 0;
		do_move_abs(dragged.x, dragged.y, dragged.x_range,
			    dragged.y_range, mouse.cursor_on >= 0);
	}

	/* Provide 3 button emulation on 2 button mice,
	   But only when PC Mouse Mode is set, otherwise
	   Microsoft Mode = 2 buttons.
	   Middle press if left/right pressed together.
	   Middle release done for us by the above routines.
	   This bit also resets the left/right button, because
	   we can't have all three buttons pressed at once.
	   Well, we could, but not under emulation on 2 button mice.
	   Alan Hourihane */
	if (mice->emulate3buttons && mouse.threebuttons) {
		if (lbutton && rbutton) {
			mbutton = 1;  /* Set middle button */
		}
	}
	/*
	 * update the event mask
	 */
	if (lbutton != mouse.lbutton) {
	    mouse.lbutton = lbutton;
	    mouse_lb();
	}
	if (mouse.threebuttons && mbutton != mouse.mbutton) {
	    mouse.mbutton = mbutton;
	    mouse_mb();
	}
	if (rbutton != mouse.rbutton) {
	    mouse.rbutton = rbutton;
	    mouse_rb();
	}
}

static void int33_mouse_move_button(int num, int press, void *udata)
{
	if (dragged.skipped) {
		dragged.skipped = 0;
		do_move_abs(dragged.x, dragged.y, dragged.x_range,
			    dragged.y_range, mouse.cursor_on >= 0);
	}

	switch (num) {
	case 0:
		if (press != mouse.lbutton) {
		    mouse.lbutton = press;
		    mouse_lb();
		}
		break;
	case 1:
		if (mouse.threebuttons && press != mouse.mbutton) {
		    mouse.mbutton = press;
		    mouse_mb();
		}
		break;
	case 2:
		if (press != mouse.rbutton) {
		    mouse.rbutton = press;
		    mouse_rb();
		}
		break;
	}
}

static void int33_mouse_move_wheel(int dy, void *udata)
{
	m_printf("MOUSE: wheel movement %i\n", dy);
	mouse.wmcount += dy;
	mouse.wmx = MOUSE_RX;
	mouse.wmy = MOUSE_RY;
	mouse_delta(DELTA_WHEEL);
}

static void int33_mouse_move_relative(int dx, int dy, int x_range, int y_range,
	void *udata)
{
	if (mouse.px_range != x_range || mouse.py_range != y_range)
		set_px_ranges(x_range, y_range);
	add_px(dx, dy);
	mouse.x_delta = mouse.y_delta = 0;
	mouse.need_resync = 1;

	m_printf("mouse_move_relative(%d, %d) -> %d %d \n",
		 dx, dy, get_mx(), get_my());

	/*
	 * update the event mask
	 */
	if (dx || dy)
	   mouse_move(0);
}

static void int33_mouse_move_mickeys(int dx, int dy, void *udata)
{
	if (mouse.px_range != 1 || mouse.py_range != 1)
		set_px_ranges(1, 1);
	add_mk(dx, dy);

	m_printf("mouse_move_mickeys(%d, %d) -> %d %d \n",
		 dx, dy, get_mx(), get_my());

	/*
	 * update the event mask
	 */
	if (dx || dy)
	   mouse_move(0);
}

static int move_abs_mickeys(int dx, int dy, int x_range, int y_range)
{

	int ret = 0, oldmx = mickeyx(), oldmy = mickeyy();
	long long mdx, mdy;
	scale_coords_spd_unsc_mk(dx, dy, &mdx, &mdy);

	if (mdx || mdy) {
		add_mickey_coords(mdx, mdy);
		ret = 1;
	}
	m_printf("mouse_move_absolute dx:%d dy:%d mickeys: %d %d -> %d %d\n",
			 dx, dy, oldmx, oldmy, mickeyx(), mickeyy());

	return ret;
}

static int move_abs_coords(int x, int y, int x_range, int y_range, int vis)
{
	long long new_x, new_y;
	int clipped, c_x, c_y, oldx = get_mx(), oldy = get_my();

	scale_coords(x, y, x_range, y_range, &new_x, &new_y);
	/* for visible cursor always recalc deltas */
	if (vis)
		mouse.x_delta = mouse.y_delta = 0;
	clipped = mouse_clip_coords2(new_x + mouse.x_delta,
		    new_y + mouse.y_delta, &c_x, &c_y);
	/* we don't allow DOS prog to grab mouse pointer by locking it
	 * inside a clipping region. So just update deltas. If cursor
	 * is visible, we always try to keep them at 0. */
	if (clipped) {
	    mouse.x_delta = c_x - new_x;
	    mouse.y_delta = c_y - new_y;
	}
	setxy(c_x, c_y);

	m_printf("mouse_move_absolute(%d, %d, %d, %d) %d %d -> %d %d \n",
		 x, y, x_range, y_range, oldx, oldy, get_mx(), get_my());

	return 1;
}

static void do_move_abs(int x, int y, int x_range, int y_range, int vis)
{
	int moved = 0;

	if (mouse.px_range != x_range || mouse.py_range != y_range)
		set_px_ranges(x_range, y_range);

	moved |= move_abs_mickeys(x - mouse.px_abs, y - mouse.py_abs,
		    x_range, y_range);
	moved |= move_abs_coords(x, y, x_range, y_range, vis);

	mouse.px_abs = x;
	mouse.py_abs = y;

	/*
	 * update the event mask
	 */
	if (moved)
		mouse_move(1);
}

static void int33_mouse_move_absolute(int x, int y, int x_range, int y_range,
	int vis, void *udata)
{
	/* give an app some time to chew dragging */
	if (dragged.cnt > 1) {
		m_printf("MOUSE: deferring abs move\n");
		dragged.skipped++;
		dragged.x = x;
		dragged.y = y;
		dragged.x_range = x_range;
		dragged.y_range = y_range;
		return;
	}
	if (mouse.need_resync) {
		/* this is mainly for Carmageddon. It can't tolerate the
		 * mickeys getting out of sync with coords. So we recalculate
		 * the position when switching from rel to abs mode. */
		int mx_range, my_range;
		mouse.need_resync = 0;
		/* for invisible cursor update deltas and return */
		if (mouse.cursor_on < 0) {
			long long new_x, new_y;
			scale_coords(x, y, x_range, y_range, &new_x, &new_y);
			mouse.x_delta = get_mx() - new_x;
			mouse.y_delta = get_my() - new_y;
			mouse.px_abs = x;
			mouse.py_abs = y;
			return;
		}
		get_scale_range(&mx_range, &my_range);
		mouse.px_abs = mouse.unsc_x * mouse.speed_x /
			    (mice->init_speed_x * mx_range);
		mouse.py_abs = mouse.unsc_y * mouse.speed_y /
			    (mice->init_speed_y * my_range);
		m_printf("MOUSE: synced coords, x:%i y:%i\n",
			    mouse.px_abs, mouse.py_abs);
	}
	do_move_abs(x, y, x_range, y_range, vis);
}

/* this is for buggy apps that use the mickey tracking and have
 * the unreachable areas in ungrabbed mode, because of the resolution
 * mismatch (they organize their own resolution via mickey tracking).
 * Transport Tycoon is an example.
 * The idea is that having an unreachable area only at the bottom is
 * better than randomly in different places, like at the top.
 */
static void int33_mouse_drag_to_corner(int x_range, int y_range, void *udata)
{
	m_printf("MOUSE: drag to corner, %i\n", dragged.cnt);
	if (dragged.cnt > 1)
		return;
	int33_mouse_move_relative(-3 * x_range, -3 * y_range, x_range, y_range,
		udata);
	dragged.cnt = 5;
	dragged.skipped = 0;
	mouse.px_abs = 0;
	mouse.py_abs = 0;
	mouse.x_delta = mouse.y_delta = 0;
	mouse.need_resync = 0;
}

/*
 * add the event to the current event mask
 */
void
mouse_delta(int event)
{
	mouse_events |= event;
	reset_idle(0);
}

/*
 * call PS/2 (int15) event handler
 */
static void call_int15_mouse_event_handler(void)
{
  int status;
  unsigned int ssp, sp;
  int dx, dy, cnt = 0;

  rm_stack_enter();
  ssp = SEGOFF2LINEAR(SREG(ss), 0);
  sp = LWORD(esp);

  do {
    cnt++;
    dx = mickeyx();
    /* PS/2 wants the y direction reversed */
    dy = -mickeyy();

    status = (mouse.rbutton ? 2 : 0) | (mouse.lbutton ? 1 : 0) | 8;
    if (mouse.threebuttons)
      status |= (mouse.mbutton ? 4 : 0);
    /* overflow; many DOS drivers do a simple cbw on the value... */
    if (dx < -128 || dx > 127)
      dx = dx < -128 ? -128 : 127;
    if (dy < -128 || dy > 127)
      dy = dy < -128 ? -128 : 127;
    mouse.unscm_x -= get_unsc_mk_x(dx * 8);
    mouse.unscm_y += get_unsc_mk_y(dy * 8);
    /* we'll call the handler again */
    if (dx < 0)
      status |= 0x10;
    if (dy < 0)
      status |= 0x20;

    m_printf("PS2MOUSE data: dx=%hhx, dy=%hhx, status=%hx\n", (Bit8u)dx, (Bit8u)dy, (unsigned short)status);
    pushw(ssp, sp, status);
    pushw(ssp, sp, dx & 0xff);
    pushw(ssp, sp, dy & 0xff);
    pushw(ssp, sp, 0);
    LWORD(esp) -= 8;

    /* jump to mouse cs:ip */
    m_printf("PS2MOUSE: .........jumping to %04x:%04x %i times\n",
	    mouse.ps2.cs, mouse.ps2.ip, cnt);
    do_call_back(mouse.ps2.cs, mouse.ps2.ip);
    LWORD(esp) += 8;
  } while (mickeyx() || mickeyy());

  rm_stack_leave();
}

static void call_int33_mouse_event_handler(void)
{
    rm_stack_enter();
    LWORD(eax) = mouse_events;
    LWORD(ecx) = MOUSE_RX;
    LWORD(edx) = MOUSE_RY;
    LWORD(esi) = mickeyx();
    LWORD(edi) = mickeyy();
    LO(bx) = (mouse.rbutton ? 2 : 0) | (mouse.lbutton ? 1 : 0);
    if (mouse.threebuttons)
      LO(bx) |= (mouse.mbutton ? 4 : 0);
    HI(bx) = mouse.wmcount;
    mouse.wmcount = 0;

    /* jump to mouse cs:ip */
    m_printf("MOUSE: event %d, x %d(%d), y %d(%d), mx %d, my %d, b %x\n",
	     mouse_events, MOUSE_RX, get_mx(), MOUSE_RY, get_my(), mickeyx(),
	     mickeyy(), LWORD(ebx));
    m_printf("MOUSE: .........jumping to %04x:%04x\n", mouse.cs, mouse.ip);
    SREG(ds) = mouse.cs;		/* put DS in user routine */
    do_call_back(mouse.cs, mouse.ip);
    rm_stack_leave();

    /* When mouse is dragged to the corner with mouse_drag_to_corner(),
     * the mickey counters are going crazy. This is not a problem when
     * an app _reads_ the counters - they are then reset. However, if it
     * doesn't read them but instead monitors them via callback (Carmageddon),
     * then we need to sync them with coords eventually, or, otherwise, drop
     * the dragging hack entirely. Since the dragging hack is pretty
     * useful and the idea of monitoring mickeys is outright silly (and
     * almost no progs do that), the dragging hack stays, and the hack
     * below compensates its effect on mickeys. - stsp */
    if (dragged.cnt) {
      /* syncing mickey counters with coords is silly, I know, but
       * Carmageddon needs this after dragging mouse to the corner. */
      setmxy(get_mx() * mouse.speed_x, get_my() * mouse.speed_y);
      dragged.cnt = 0;
      m_printf("MOUSE: mickey synced with coords, x:%i->%i y:%i->%i\n",
          get_mx(), mickeyx(), get_my(), mickeyy());
    }
}

/* this function is called from int74 via inte6 */
static void call_mouse_event_handler(void *arg)
{
  int handled = 0;

  if ((mouse.mask & mouse_events) && (mouse.cs || mouse.ip)) {
    call_int33_mouse_event_handler();
    mouse_events = 0;
    handled = 1;
  } else {
    m_printf("MOUSE: Skipping event handler, "
	       "mask=0x%x, ev=0x%x, cs=0x%x, ip=0x%x\n",
	       mouse.mask, mouse_events, mouse.cs, mouse.ip);
  }

  if (handled && dragged.skipped) {
    dragged.skipped = 0;
    do_move_abs(dragged.x, dragged.y, dragged.x_range, dragged.y_range,
        mouse.cursor_on >= 0);
  }
}

/* unconditional mouse cursor update */
static void mouse_do_cur(int callback)
{
  if (mouse.gfx_cursor) {
    graph_cursor();
  }
  else {
    text_cursor();
  }

  if (mice->native_cursor || !callback)
    return;
#if 0
  /* this callback is used to e.g. warp the X cursor if int33/ax=4
     requested it to be moved */
  mouse_client_set_cursor(mouse.cursor_on == 0?1: 0,
		    get_mx() - mouse.x_delta,
		    get_my() - mouse.y_delta,
		    mouse.maxx - MOUSE_MINX +1, mouse.maxy - MOUSE_MINY +1);
#endif
}

/* conditionally update the mouse cursor only if it's changed position. */
static void
mouse_update_cursor(void)
{
	/* sigh, too many programs seem to expect the mouse cursor
		to magically redraw itself, so we'll bend to their will... */
	if (MOUSE_RX != mouse.oldrx || MOUSE_RY != mouse.oldry) {
		mouse_do_cur(0);
		mouse.oldrx = MOUSE_RX;
		mouse.oldry = MOUSE_RY;
	}
}

void
text_cursor(void)
{
  unsigned int p;
  int offset;
  int cx, cy;
  int co = READ_WORD(BIOS_SCREEN_COLUMNS);
  p = screen_adr(mouse.display_page);
  offset = mouse_erase.x*2;
  cx = MOUSE_RX >> mouse.xshift;
  cy = MOUSE_RY >> mouse.yshift;

  if (mouse_erase.drawn) {
	/* only erase the mouse cursor if it's the same thing we
		drew; some applications seem to reset the mouse
		*after* clearing the screen and we end up leaving
		glitches behind. */
	if (vga_read_word(p + offset) == mouse_erase.backingstore.text[1])
	  vga_write_word(p + offset, mouse_erase.backingstore.text[0]);
	mouse_erase.drawn = FALSE;
  }

  if (mouse.cursor_on != 0 || !mice->native_cursor)
  	return;

  /* remember where we drew this. */
  mouse_erase.x = cx + cy * co;
  offset = mouse_erase.x*2;
  mouse_erase.backingstore.text[0] = vga_read_word(p + offset);

  /* draw it. */
  mouse_erase.backingstore.text[1] =
    (vga_read_word(p + offset) & mouse.textscreenmask) ^ mouse.textcursormask;
  vga_write_word(p + offset, mouse_erase.backingstore.text[1]);

  mouse_erase.drawn = TRUE;
}

void
graph_cursor(void)
{
  erase_graphics_cursor(&mouse_erase);

  /* draw_graphics_cursor wants screen coordinates, we have coordinates
  	based on width of 640; hotspot is always in screen coordinates. */
  if (mouse.cursor_on == 0 && mice->native_cursor)
	  draw_graphics_cursor(MOUSE_RX >> mouse.xshift, MOUSE_RY >> mouse.yshift,
		mouse.hotx,mouse.hoty,16,16,&mouse_erase);
}


static void mouse_curtick(void)
{
  if (!mice->intdrv)
    return;

//  if (debug_level('m') >= 9)
//    m_printf("MOUSE: curtick x:%d  y:%d\n", MOUSE_RX, MOUSE_RY);

  /* HACK: we need some time for an app to sense the dragging event */
  if (dragged.cnt > 1) {
    dragged.cnt--;
  } else if (dragged.skipped) {
    dragged.skipped = 0;
    do_move_abs(dragged.x, dragged.y, dragged.x_range, dragged.y_range,
        mouse.cursor_on >= 0);
  }
  if (mouse.cursor_on != 0)
    return;

  /* we used to do an unconditional update here, but that causes a
  	distracting flicker in the mouse cursor. */
  mouse_update_cursor();
}

static enum VirqSwRet do_mouse_irq(void *arg)
{
  int ret = VIRQ_SWRET_DONE;

  if (mouse.ps2.state && (mouse.ps2.cs || mouse.ps2.ip))
    return ret;
  if (mouse_events) {
    coopth_start(mouse_tid, NULL);
    ret = VIRQ_SWRET_BH;
  }
  return ret;
}

static enum VirqHwRet do_mouse_fifo(void *arg)
{
  int cnt = mousedrv_process_fifo("int33 mouse");
  if (cnt == -1) {
    error("mouse fifo empty\n");
    return VIRQ_HWRET_DONE;
  }
  if (mouse_events)
    pic_request(12);  // for ps2
  return (cnt ? VIRQ_HWRET_CONT : VIRQ_HWRET_DONE);
}

/*
 * DANG_BEGIN_FUNCTION mouse_init
 *
 * description:
 *  Initialize internal mouse.
 *
 * DANG_END_FUNCTION
 */
static int int33_mouse_init(void)
{
  if (!mice->intdrv)
    return 0;

  /* set minimum internal resolution
   * 640x200 is a long standing dosemu default
   * although I accidentally broke it broke it by flipping my X & Y coordinates.
   * -- Eric Biederman 29 May 2000
   */
  mouse.min_max_x = 640;
  mouse.min_max_y = 200;

  mouse.px_range = mouse.py_range = 1;
  mouse.px_abs = mouse.py_abs = 0;

  /* we'll admit we have three buttons if the user asked us to emulate
  	one or else we think the mouse actually has a third button. */
  if (mice->emulate3buttons || mice->has3buttons)
  	mouse.threebuttons = TRUE;
  else
  	mouse.threebuttons = FALSE;

  mouse.enabled = FALSE;

  mice->native_cursor = 1;
  mouse.cursor_on = -1;
  mice->init_speed_x = INIT_SPEED_X;
  mice->init_speed_y = INIT_SPEED_Y;
  mouse.speed_x = INIT_SPEED_X;
  mouse.speed_y = INIT_SPEED_Y;
  mouse.sens_x = 100;
  mouse.sens_y = 100;
  mouse.threshold = 200;
  mouse.exc_lx = mouse.exc_ux = -1;
  mouse.exc_ly = mouse.exc_uy = -1;

  virq_register(VIRQ_MOUSE, do_mouse_fifo, do_mouse_irq, NULL);
  mouse_tid = coopth_create("mouse", call_mouse_event_handler);
  sigalrm_register_handler(mouse_curtick);

  m_printf("MOUSE: INIT complete\n");
  return 1;
}

static int int33_mouse_accepts(int from, void *udata)
{
  if (!mice->intdrv)
    return 0;
  /* if commouse.c is active, we only accept events from it,
   * and nothing else. Those events ignore .accepts member. */
  if (mice->com != -1)
    return 0;
  /* for 2 mice see if source is ours */
  if (mice->type != mice->dev_type)
    return (from == mice->type);
  return 1;
}

static void fifo_notify(void *udata)
{
  virq_raise(VIRQ_MOUSE);
}

void mouse_late_init(void)
{
  if (!mice->intdrv) return;

  mouse.enabled = FALSE;
  mouse.cursor_on = -1;
  mouse_client_show_cursor(0);
}

void mouse_set_win31_mode(void)
{
  if (!mice->intdrv) {
    CARRY;
    return;
  }

  mouse.maxx = 65535;
  mouse.maxy = 65535;
  mouse.win31_mode = 1;
  m_printf("MOUSE: Enabled win31 mode\n");

  LWORD(eax) = 0;
}

void
dosemu_mouse_close(void)
{
  mouse_client_close();
}

/* TO DO LIST: (in no particular order)
	- play with acceleration profiles more
*/

struct mouse_drv int33_mouse = {
  .init = int33_mouse_init,
  .accepts = int33_mouse_accepts,
  .move_button = int33_mouse_move_button,
  .move_buttons = int33_mouse_move_buttons,
  .move_wheel = int33_mouse_move_wheel,
  .move_relative = int33_mouse_move_relative,
  .move_mickeys = int33_mouse_move_mickeys,
  .move_absolute = int33_mouse_move_absolute,
  .drag_to_corner = int33_mouse_drag_to_corner,
  .enable_native_cursor = int33_mouse_enable_native_cursor,
  .name = "int33 mouse",
};

CONSTRUCTOR(static void int33_mouse_register(void))
{
  register_mouse_driver_buffered(&int33_mouse, fifo_notify);
}
