#define NDEBUG
+#include "ntvdm.h"
#include "emulator.h"
+
#include "cpu/cpu.h"
#include "int32.h"
+#include "hardware/mouse.h"
+#include "hardware/ps2.h"
+#include "hardware/pic.h"
+#include "hardware/video/vga.h"
#include "mouse32.h"
#include "bios/bios.h"
+#include "bios/bios32/bios32p.h"
#include "io.h"
#include "dos32krnl/dos.h"
/* PRIVATE VARIABLES **********************************************************/
-static BOOLEAN DriverEnabled = TRUE;
-static MOUSE_DRIVER_STATE DriverState;
+// FIXME: Because I don't know a better place to store the string
+// I temporarily put it in BIOS space. This need to be moved to a
+// proper place when this driver is interfaced correctly with DOS.
+#define COPYRIGHT_POINTER MAKELONG(0xE100, 0xF000)
+static const CHAR MouseCopyright[] = "ROS PS/2 16/32-bit Mouse Driver Compatible MS-MOUSE 6.26 Copyright (C) ReactOS Team 1996-2015";
+
+// See FIXME from above.
+#define VERSION_POINTER MAKELONG(0xE160, 0xF000)
+static PWORD Version;
-/**/
-COORD DosNewPosition;
-WORD DosButtonState;
-/**/
+#define MICKEYS_PER_CELL_HORIZ 8
+#define MICKEYS_PER_CELL_VERT 16
+
+static BOOLEAN DriverEnabled = FALSE;
+static MOUSE_DRIVER_STATE DriverState;
+static DWORD OldIrqHandler;
+static DWORD OldIntHandler;
/* PRIVATE FUNCTIONS **********************************************************/
static VOID PaintMouseCursor(VOID)
{
+ COORD Position = DriverState.Position;
+
+ /* Apply the clipping rectangle */
+ if (Position.X < DriverState.MinX) Position.X = DriverState.MinX;
+ if (Position.X > DriverState.MaxX) Position.X = DriverState.MaxX;
+ if (Position.Y < DriverState.MinY) Position.Y = DriverState.MinY;
+ if (Position.Y > DriverState.MaxY) Position.Y = DriverState.MaxY;
+
if (Bda->VideoMode <= 3)
{
WORD Character;
+ WORD CellX = Position.X / 8;
+ WORD CellY = Position.Y / 8;
DWORD VideoAddress = TO_LINEAR(TEXT_VIDEO_SEG, Bda->VideoPage * Bda->VideoPageSize);
EmulatorReadMemory(&EmulatorContext,
VideoAddress
- + (DriverState.Position.Y * Bda->ScreenColumns
- + DriverState.Position.X) * sizeof(WORD),
+ + (CellY * Bda->ScreenColumns + CellX) * sizeof(WORD),
(LPVOID)&Character,
sizeof(WORD));
EmulatorWriteMemory(&EmulatorContext,
VideoAddress
- + (DriverState.Position.Y * Bda->ScreenColumns
- + DriverState.Position.X) * sizeof(WORD),
+ + (CellY * Bda->ScreenColumns + CellX) * sizeof(WORD),
(LPVOID)&Character,
sizeof(WORD));
}
static VOID EraseMouseCursor(VOID)
{
+ COORD Position = DriverState.Position;
+
+ /* Apply the clipping rectangle */
+ if (Position.X < DriverState.MinX) Position.X = DriverState.MinX;
+ if (Position.X > DriverState.MaxX) Position.X = DriverState.MaxX;
+ if (Position.Y < DriverState.MinY) Position.Y = DriverState.MinY;
+ if (Position.Y > DriverState.MaxY) Position.Y = DriverState.MaxY;
+
if (Bda->VideoMode <= 3)
{
+ WORD CellX = Position.X / 8;
+ WORD CellY = Position.Y / 8;
DWORD VideoAddress = TO_LINEAR(TEXT_VIDEO_SEG, Bda->VideoPage * Bda->VideoPageSize);
EmulatorWriteMemory(&EmulatorContext,
VideoAddress
- + (DriverState.Position.Y * Bda->ScreenColumns
- + DriverState.Position.X) * sizeof(WORD),
+ + (CellY * Bda->ScreenColumns + CellX) * sizeof(WORD),
(LPVOID)&DriverState.Character,
sizeof(WORD));
}
}
}
+static VOID ToMouseCoordinates(PCOORD Position)
+{
+ COORD Resolution = VgaGetDisplayResolution();
+ WORD Width = DriverState.MaxX - DriverState.MinX + 1;
+ WORD Height = DriverState.MaxY - DriverState.MinY + 1;
+
+ if (!VgaGetDoubleVisionState(NULL, NULL))
+ {
+ Resolution.X *= 8;
+ Resolution.Y *= 8;
+ }
+
+ Position->X = DriverState.MinX + ((Position->X * Width) / Resolution.X);
+ Position->Y = DriverState.MinY + ((Position->Y * Height) / Resolution.Y);
+}
+
+static VOID FromMouseCoordinates(PCOORD Position)
+{
+ COORD Resolution = VgaGetDisplayResolution();
+ WORD Width = DriverState.MaxX - DriverState.MinX + 1;
+ WORD Height = DriverState.MaxY - DriverState.MinY + 1;
+
+ if (!VgaGetDoubleVisionState(NULL, NULL))
+ {
+ Resolution.X *= 8;
+ Resolution.Y *= 8;
+ }
+
+ Position->X = ((Position->X - DriverState.MinX) * Resolution.X) / Width;
+ Position->Y = ((Position->Y - DriverState.MinY) * Resolution.Y) / Height;
+}
+
static VOID CallMouseUserHandlers(USHORT CallMask)
{
USHORT i;
USHORT AX, BX, CX, DX, SI, DI;
+ COORD Position = DriverState.Position;
+
+ ToMouseCoordinates(&Position);
/* Call handler 0 */
if ((DriverState.Handler0.CallMask & CallMask) != 0 &&
setAX(CallMask);
setBX(DriverState.ButtonState);
- setCX(DriverState.Position.X);
- setDX(DriverState.Position.Y);
- setSI(DriverState.MickeysPerCellHoriz);
- setDI(DriverState.MickeysPerCellVert);
+ setCX(Position.X);
+ setDX(Position.Y);
+ setSI(MICKEYS_PER_CELL_HORIZ);
+ setDI(MICKEYS_PER_CELL_VERT);
DPRINT("Calling Handler0 %04X:%04X with CallMask 0x%04X\n",
HIWORD(DriverState.Handler0.Callback),
setDI(DI);
}
- for (i = 0; i < sizeof(DriverState.Handlers)/sizeof(DriverState.Handlers[0]); ++i)
+ for (i = 0; i < ARRAYSIZE(DriverState.Handlers); ++i)
{
/* Call the suitable handlers */
if ((DriverState.Handlers[i].CallMask & CallMask) != 0 &&
setAX(CallMask);
setBX(DriverState.ButtonState);
- setCX(DriverState.Position.X);
- setDX(DriverState.Position.Y);
- setSI(DriverState.MickeysPerCellHoriz);
- setDI(DriverState.MickeysPerCellVert);
+ setCX(Position.X);
+ setDX(Position.Y);
+ setSI(MICKEYS_PER_CELL_HORIZ);
+ setDI(MICKEYS_PER_CELL_VERT);
DPRINT1("Calling Handler[%d] %04X:%04X with CallMask 0x%04X\n",
i,
}
}
+static inline VOID DosUpdatePosition(PCOORD NewPosition)
+{
+ COORD Resolution = VgaGetDisplayResolution();
+
+ /* Check for text mode */
+ if (!VgaGetDoubleVisionState(NULL, NULL))
+ {
+ Resolution.X *= 8;
+ Resolution.Y *= 8;
+ }
+
+ if (DriverState.ShowCount > 0) EraseMouseCursor();
+ DriverState.Position = *NewPosition;
+ if (DriverState.ShowCount > 0) PaintMouseCursor();
+
+ /* Call the mouse handlers */
+ CallMouseUserHandlers(0x0001); // We use MS MOUSE v1.0+ format
+}
+
+static inline VOID DosUpdateButtons(BYTE ButtonState)
+{
+ USHORT i;
+ USHORT CallMask = 0x0000; // We use MS MOUSE v1.0+ format
+
+ for (i = 0; i < NUM_MOUSE_BUTTONS; i++)
+ {
+ BOOLEAN OldState = (DriverState.ButtonState >> i) & 1;
+ BOOLEAN NewState = (ButtonState >> i) & 1;
+
+ if (NewState > OldState)
+ {
+ /* Mouse press */
+ DriverState.PressCount[i]++;
+ DriverState.LastPress[i] = DriverState.Position;
+
+ CallMask |= (1 << (2 * i + 1));
+ }
+ else if (NewState < OldState)
+ {
+ /* Mouse release */
+ DriverState.ReleaseCount[i]++;
+ DriverState.LastRelease[i] = DriverState.Position;
+
+ CallMask |= (1 << (2 * i + 2));
+ }
+ }
+
+ DriverState.ButtonState = ButtonState;
+
+ /* Call the mouse handlers */
+ CallMouseUserHandlers(CallMask);
+}
+
+static VOID WINAPI DosMouseIrq(LPWORD Stack)
+{
+ BYTE Flags;
+ SHORT DeltaX, DeltaY;
+ COORD Position;
+ BYTE ButtonState;
+
+ /* Read the whole packet at once */
+ Flags = IOReadB(PS2_DATA_PORT);
+ PS2PortQueueRead(1);
+ DeltaX = IOReadB(PS2_DATA_PORT);
+ PS2PortQueueRead(1);
+ DeltaY = IOReadB(PS2_DATA_PORT);
+
+ /* Adjust the sign */
+ if (Flags & MOUSE_X_SIGN) DeltaX = -DeltaX;
+ if (Flags & MOUSE_Y_SIGN) DeltaY = -DeltaY;
+
+ /* Update the counters */
+ DriverState.HorizCount += DeltaX;
+ DriverState.VertCount += DeltaY;
+
+ /*
+ * Get the absolute position directly from the mouse, this is the only
+ * way to perfectly synchronize the host and guest mouse pointer.
+ */
+ MouseGetDataFast(&Position, &ButtonState);
+
+ /* Call the update subroutines */
+ DosUpdatePosition(&Position);
+ DosUpdateButtons(ButtonState);
+
+ /* Complete the IRQ */
+ PicIRQComplete(Stack);
+}
+
static VOID WINAPI DosMouseService(LPWORD Stack)
{
switch (getAX())
{
SHORT i;
- DriverEnabled = TRUE;
DriverState.ShowCount = 0;
DriverState.ButtonState = 0;
+ /* Initialize the default clipping range */
+ DriverState.MinX = 0;
+ DriverState.MaxX = MOUSE_MAX_HORIZ - 1;
+ DriverState.MinY = 0;
+ DriverState.MaxY = MOUSE_MAX_VERT - 1;
+
/* Set the default text cursor */
DriverState.TextCursor.ScreenMask = 0xFFFF; /* Display everything */
DriverState.TextCursor.CursorMask = 0xFF00; /* ... but with inverted attributes */
DriverState.PressCount[i] = DriverState.ReleaseCount[i] = 0;
}
- /* Initialize the resolution */
- DriverState.MickeysPerCellHoriz = 8;
- DriverState.MickeysPerCellVert = 16;
-
/* Return mouse information */
setAX(0xFFFF); // Hardware & driver installed
setBX(NUM_MOUSE_BUTTONS);
case 0x01:
{
DriverState.ShowCount++;
- if (DriverState.ShowCount > 0) PaintMouseCursor();
+ if (DriverState.ShowCount == 1) PaintMouseCursor();
break;
}
case 0x02:
{
DriverState.ShowCount--;
- if (DriverState.ShowCount <= 0) EraseMouseCursor();
+ if (DriverState.ShowCount == 0) EraseMouseCursor();
break;
}
/* Return Position And Button Status */
case 0x03:
{
+ COORD Position = DriverState.Position;
+ ToMouseCoordinates(&Position);
+
setBX(DriverState.ButtonState);
- setCX(DriverState.Position.X);
- setDX(DriverState.Position.Y);
+ setCX(Position.X);
+ setDX(Position.Y);
break;
}
/* Position Mouse Cursor */
case 0x04:
{
- POINT Point;
-
- Point.x = getCX();
- Point.y = getDX();
-
- ClientToScreen(GetConsoleWindow(), &Point);
- SetCursorPos(Point.x, Point.y);
+ COORD Position = { getCX(), getDX() };
+ FromMouseCoordinates(&Position);
+ DriverState.Position = Position;
break;
}
case 0x05:
{
WORD Button = getBX();
+ COORD LastPress = DriverState.LastPress[Button];
+ ToMouseCoordinates(&LastPress);
setAX(DriverState.ButtonState);
setBX(DriverState.PressCount[Button]);
- setCX(DriverState.LastPress[Button].X);
- setDX(DriverState.LastPress[Button].Y);
+ setCX(LastPress.X);
+ setDX(LastPress.Y);
/* Reset the counter */
DriverState.PressCount[Button] = 0;
case 0x06:
{
WORD Button = getBX();
+ COORD LastRelease = DriverState.LastRelease[Button];
+ ToMouseCoordinates(&LastRelease);
setAX(DriverState.ButtonState);
setBX(DriverState.ReleaseCount[Button]);
- setCX(DriverState.LastRelease[Button].X);
- setDX(DriverState.LastRelease[Button].Y);
+ setCX(LastRelease.X);
+ setDX(LastRelease.Y);
/* Reset the counter */
DriverState.ReleaseCount[Button] = 0;
}
+ /* Define Horizontal Cursor Range */
+ case 0x07:
+ {
+ WORD Min = getCX();
+ WORD Max = getDX();
+
+ if (!VgaGetDoubleVisionState(NULL, NULL))
+ {
+ /* Text mode */
+ Min &= ~0x07;
+ Max |= 0x07;
+ }
+
+ DPRINT("Setting mouse horizontal range: %u - %u\n", Min, Max);
+ DriverState.MinX = Min;
+ DriverState.MaxX = Max;
+ break;
+ }
+
+ /* Define Vertical Cursor Range */
+ case 0x08:
+ {
+ WORD Min = getCX();
+ WORD Max = getDX();
+
+ if (!VgaGetDoubleVisionState(NULL, NULL))
+ {
+ /* Text mode */
+ Min &= ~0x07;
+ Max |= 0x07;
+ }
+
+ DPRINT("Setting mouse vertical range: %u - %u\n", Min, Max);
+ DriverState.MinY = Min;
+ DriverState.MaxY = Max;
+ break;
+ }
+
/* Define Graphics Cursor */
case 0x09:
{
/* Define Mickey/Pixel Ratio */
case 0x0F:
{
- DriverState.MickeysPerCellHoriz = getCX();
- DriverState.MickeysPerCellVert = getDX();
+ /* This call should be completely ignored */
+ break;
+ }
+
+ /* Define Double-Speed Threshold */
+ case 0x13:
+ {
+ DPRINT1("INT 33h, AH=13h: Mouse double-speed threshold is UNSUPPORTED\n");
break;
}
* Find the handler entry corresponding to the given
* callback and undefine it.
*/
- for (i = 0; i < sizeof(DriverState.Handlers)/sizeof(DriverState.Handlers[0]); ++i)
+ for (i = 0; i < ARRAYSIZE(DriverState.Handlers); ++i)
{
if (DriverState.Handlers[i].Callback == Callback)
{
* Find the handler entry corresponding to the given
* callmask and undefine it.
*/
- for (i = 0; i < sizeof(DriverState.Handlers)/sizeof(DriverState.Handlers[0]); ++i)
+ for (i = 0; i < ARRAYSIZE(DriverState.Handlers); ++i)
{
if (DriverState.Handlers[i].CallMask == CallMask)
{
USHORT EmptyHandler = 0xFFFF; // Invalid handler
- for (i = 0; i < sizeof(DriverState.Handlers)/sizeof(DriverState.Handlers[0]); ++i)
+ for (i = 0; i < ARRAYSIZE(DriverState.Handlers); ++i)
{
/* Find the first empty handler */
if (EmptyHandler == 0xFFFF &&
* an empty handler, set it.
*/
if (!Success && EmptyHandler != 0xFFFF
- /* && EmptyHandler < sizeof(DriverState.Handlers)/sizeof(DriverState.Handlers[0]) */)
+ /* && EmptyHandler < ARRAYSIZE(DriverState.Handlers) */)
{
DriverState.Handlers[EmptyHandler].CallMask = CallMask;
DriverState.Handlers[EmptyHandler].Callback = Callback;
/*
* Find the handler entry corresponding to the given callmask.
*/
- for (i = 0; i < sizeof(DriverState.Handlers)/sizeof(DriverState.Handlers[0]); ++i)
+ for (i = 0; i < ARRAYSIZE(DriverState.Handlers); ++i)
{
if (DriverState.Handlers[i].CallMask == CallMask)
{
break;
}
+ /* Set Mouse Sensitivity */
+ case 0x1A:
+ {
+ DPRINT1("INT 33h, AH=1Ah: Mouse sensitivity is UNSUPPORTED\n");
+ break;
+ }
+
+ /* Return Mouse Sensitivity */
+ case 0x1B:
+ {
+ DPRINT1("INT 33h, AH=1Bh: Mouse sensitivity is UNSUPPORTED\n");
+
+ /* Return default values */
+ setBX(50); // Horizontal speed
+ setCX(50); // Vertical speed
+ setDX(50); // Double speed threshold
+ break;
+ }
+
/* Disable Mouse Driver */
case 0x1F:
{
- setES(0x0000);
- setBX(0x0000);
+ /* INT 33h vector before the mouse driver was first installed */
+ setES(HIWORD(OldIntHandler));
+ setBX(LOWORD(OldIntHandler));
- DriverEnabled = FALSE;
+ DosMouseDisable();
break;
}
/* Enable Mouse Driver */
case 0x20:
{
- DriverEnabled = TRUE;
+ DosMouseEnable();
+ break;
+ }
+
+ /* Software Reset */
+ case 0x21:
+ {
+ /*
+ * See: http://www.htl-steyr.ac.at/~morg/pcinfo/hardware/interrupts/inte3sq8.htm
+ * for detailed information and differences with respect to subfunction 0x00:
+ * http://www.htl-steyr.ac.at/~morg/pcinfo/hardware/interrupts/inte3j74.htm
+ */
+
+ SHORT i;
+
+ DriverState.ShowCount = 0;
+ DriverState.ButtonState = 0;
+
+ /* Initialize the default clipping range */
+ DriverState.MinX = 0;
+ DriverState.MaxX = MOUSE_MAX_HORIZ - 1;
+ DriverState.MinY = 0;
+ DriverState.MaxY = MOUSE_MAX_VERT - 1;
+
+ /* Initialize the counters */
+ DriverState.HorizCount = DriverState.VertCount = 0;
+
+ for (i = 0; i < NUM_MOUSE_BUTTONS; i++)
+ {
+ DriverState.PressCount[i] = DriverState.ReleaseCount[i] = 0;
+ }
+
+ /* Return mouse information */
+ setAX(0xFFFF); // Hardware & driver installed
+ setBX(NUM_MOUSE_BUTTONS);
+
+ break;
+ }
+
+ /* Get Software Version, Mouse Type, and IRQ Number, compatible MS MOUSE v6.26+ */
+ case 0x24:
+ {
+ setBX(MOUSE_VERSION); // Version Number
+
+ // FIXME: To be determined at runtime!
+ setCH(0x04); // PS/2 Type
+ setCL(0x00); // PS/2 Interrupt
+
+ break;
+ }
+
+ /* Return Pointer to Copyright String */
+ case 0x4D:
+ {
+ setES(HIWORD(COPYRIGHT_POINTER));
+ setDI(LOWORD(COPYRIGHT_POINTER));
+ break;
+ }
+
+ /* Get Version String (pointer) */
+ case 0x6D:
+ {
+ /*
+ * The format of the version "string" is:
+ * Offset Size Description
+ * 00h BYTE major version
+ * 01h BYTE minor version (BCD)
+ */
+ setES(HIWORD(VERSION_POINTER));
+ setDI(LOWORD(VERSION_POINTER));
break;
}
/* PUBLIC FUNCTIONS ***********************************************************/
+VOID DosMouseEnable(VOID)
+{
+ if (!DriverEnabled)
+ {
+ DriverEnabled = TRUE;
+
+ /* Get the old IRQ handler */
+ OldIrqHandler = ((PDWORD)BaseAddress)[MOUSE_IRQ_INT];
+
+ /* Set the IRQ handler */
+ RegisterDosInt32(MOUSE_IRQ_INT, DosMouseIrq);
+ }
+}
+
+VOID DosMouseDisable(VOID)
+{
+ if (DriverEnabled)
+ {
+ /* Restore the old IRQ handler */
+ ((PDWORD)BaseAddress)[MOUSE_IRQ_INT] = OldIrqHandler;
+
+ DriverEnabled = FALSE;
+ }
+}
+
VOID DosMouseUpdatePosition(PCOORD NewPosition)
{
SHORT DeltaX = NewPosition->X - DriverState.Position.X;
if (!DriverEnabled) return;
- DriverState.HorizCount += (DeltaX * (SHORT)DriverState.MickeysPerCellHoriz) / 8;
- DriverState.VertCount += (DeltaY * (SHORT)DriverState.MickeysPerCellVert ) / 8;
+ DriverState.HorizCount += (DeltaX * MICKEYS_PER_CELL_HORIZ) / 8;
+ DriverState.VertCount += (DeltaY * MICKEYS_PER_CELL_VERT) / 8;
if (DriverState.ShowCount > 0) EraseMouseCursor();
DriverState.Position = *NewPosition;
/* Clear the state */
RtlZeroMemory(&DriverState, sizeof(DriverState));
+ /* Setup the version variable in BCD format, compatible MS-MOUSE */
+ Version = (PWORD)FAR_POINTER(VERSION_POINTER);
+ *Version = MAKEWORD(MOUSE_VERSION/0x0100, MOUSE_VERSION%0x0100);
+
+ /* Mouse Driver Copyright */
+ RtlCopyMemory(FAR_POINTER(COPYRIGHT_POINTER), MouseCopyright, sizeof(MouseCopyright)-1);
+
+ /* Get the old mouse service interrupt handler */
+ OldIntHandler = ((PDWORD)BaseAddress)[DOS_MOUSE_INTERRUPT];
+
/* Initialize the interrupt handler */
RegisterDosInt32(DOS_MOUSE_INTERRUPT, DosMouseService);
+ DosMouseEnable();
return TRUE;
}
VOID DosMouseCleanup(VOID)
{
+ /* Restore the old mouse service interrupt handler */
+ ((PDWORD)BaseAddress)[DOS_MOUSE_INTERRUPT] = OldIntHandler;
+
if (DriverState.ShowCount > 0) EraseMouseCursor();
+ DosMouseDisable();
}