reactos/subsystems/mvdm/ntvdm/hardware/mouse.c

480 lines
12 KiB
C

/*
* COPYRIGHT: GPL - See COPYING in the top level directory
* PROJECT: ReactOS Virtual DOS Machine
* FILE: subsystems/mvdm/ntvdm/hardware/mouse.c
* PURPOSE: PS/2 Mouse emulation
* PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
*/
/* INCLUDES *******************************************************************/
#include "ntvdm.h"
#define NDEBUG
#include <debug.h>
#include "mouse.h"
#include "ps2.h"
#include "clock.h"
#include "video/svga.h"
/**/
#include "../console/video.h"
/**/
/* PRIVATE VARIABLES **********************************************************/
static const BYTE ScrollMagic[3] = { 200, 100, 80 };
static const BYTE ExtraButtonMagic[3] = { 200, 200, 80 };
static HANDLE MouseMutex;
static PHARDWARE_TIMER StreamTimer;
static MOUSE_PACKET LastPacket;
static MOUSE_MODE Mode, PreviousMode;
static COORD Position;
static BYTE Resolution; /* Completely ignored */
static BOOLEAN Scaling; /* Completely ignored */
static BOOLEAN MouseReporting = FALSE;
static BYTE MouseId;
static ULONG ButtonState;
static SHORT HorzCounter;
static SHORT VertCounter;
static CHAR ScrollCounter;
static BOOLEAN EventsOccurred = FALSE;
static BYTE MouseDataByteWait = 0;
static BYTE ScrollMagicCounter = 0, ExtraButtonMagicCounter = 0;
static UINT MouseCycles = 10;
static BYTE MousePS2Port = 1;
/* PUBLIC VARIABLES ***********************************************************/
/* PRIVATE FUNCTIONS **********************************************************/
static VOID MouseResetConfig(VOID)
{
/* Reset the configuration to defaults */
MouseCycles = 10;
Resolution = 4;
Scaling = FALSE;
MouseReporting = FALSE;
}
static VOID MouseResetCounters(VOID)
{
/* Reset all flags and counters */
HorzCounter = VertCounter = ScrollCounter = 0;
}
static VOID MouseReset(VOID)
{
/* Reset everything */
MouseResetConfig();
MouseResetCounters();
/* Enter streaming mode and the reset the mouse ID */
Mode = MOUSE_STREAMING_MODE;
MouseId = 0;
ScrollMagicCounter = ExtraButtonMagicCounter = 0;
}
static VOID MouseGetPacket(PMOUSE_PACKET Packet)
{
/* Clear the packet */
RtlZeroMemory(Packet, sizeof(*Packet));
/* Acquire the mutex */
WaitForSingleObject(MouseMutex, INFINITE);
Packet->Flags |= MOUSE_ALWAYS_SET;
/* Set the sign flags */
if (HorzCounter < 0)
{
Packet->Flags |= MOUSE_X_SIGN;
HorzCounter = -HorzCounter;
}
if (VertCounter < 0)
{
Packet->Flags |= MOUSE_Y_SIGN;
VertCounter = -VertCounter;
}
/* Check for horizontal overflows */
if (HorzCounter > MOUSE_MAX)
{
HorzCounter = MOUSE_MAX;
Packet->Flags |= MOUSE_X_OVERFLOW;
}
/* Check for vertical overflows */
if (VertCounter > MOUSE_MAX)
{
VertCounter = MOUSE_MAX;
Packet->Flags |= MOUSE_Y_OVERFLOW;
}
/* Set the button flags */
if (ButtonState & FROM_LEFT_1ST_BUTTON_PRESSED) Packet->Flags |= MOUSE_LEFT_BUTTON;
if (ButtonState & FROM_LEFT_2ND_BUTTON_PRESSED) Packet->Flags |= MOUSE_MIDDLE_BUTTON;
if (ButtonState & RIGHTMOST_BUTTON_PRESSED) Packet->Flags |= MOUSE_RIGHT_BUTTON;
if (MouseId == 4)
{
if (ButtonState & FROM_LEFT_3RD_BUTTON_PRESSED) Packet->Extra |= MOUSE_4TH_BUTTON;
if (ButtonState & FROM_LEFT_4TH_BUTTON_PRESSED) Packet->Extra |= MOUSE_5TH_BUTTON;
}
if (MouseId >= 3)
{
/* Set the scroll counter */
Packet->Extra |= ((UCHAR)ScrollCounter & 0x0F);
}
/* Store the counters in the packet */
Packet->HorzCounter = LOBYTE(HorzCounter);
Packet->VertCounter = LOBYTE(VertCounter);
/* Reset the counters */
MouseResetCounters();
/* Release the mutex */
ReleaseMutex(MouseMutex);
}
static VOID MouseDispatchPacket(PMOUSE_PACKET Packet)
{
PS2QueuePush(MousePS2Port, Packet->Flags);
PS2QueuePush(MousePS2Port, Packet->HorzCounter);
PS2QueuePush(MousePS2Port, Packet->VertCounter);
if (MouseId >= 3) PS2QueuePush(MousePS2Port, Packet->Extra);
}
static VOID WINAPI MouseCommand(LPVOID Param, BYTE Command)
{
/* Check if we were waiting for a data byte */
if (MouseDataByteWait)
{
PS2QueuePush(MousePS2Port, MOUSE_ACK);
switch (MouseDataByteWait)
{
/* Set Resolution */
case 0xE8:
{
Resolution = Command;
break;
}
/* Set Sample Rate */
case 0xF3:
{
/* Check for the scroll wheel enabling sequence */
if (MouseId == 0)
{
if (Command == ScrollMagic[ScrollMagicCounter])
{
ScrollMagicCounter++;
if (ScrollMagicCounter == 3) MouseId = 3;
}
else
{
ScrollMagicCounter = 0;
}
}
/* Check for the 5-button enabling sequence */
if (MouseId == 3)
{
if (Command == ExtraButtonMagic[ExtraButtonMagicCounter])
{
ExtraButtonMagicCounter++;
if (ExtraButtonMagicCounter == 3) MouseId = 4;
}
else
{
ExtraButtonMagicCounter = 0;
}
}
MouseCycles = 1000 / (UINT)Command;
break;
}
default:
{
/* Shouldn't happen */
ASSERT(FALSE);
}
}
MouseDataByteWait = 0;
return;
}
/* Check if we're in wrap mode */
if (Mode == MOUSE_WRAP_MODE)
{
/*
* In this mode, we just echo whatever byte we get,
* except for the 0xEC and 0xFF commands.
*/
if (Command != 0xEC && Command != 0xFF)
{
PS2QueuePush(MousePS2Port, Command);
return;
}
}
switch (Command)
{
/* Set 1:1 Scaling */
case 0xE6:
{
Scaling = FALSE;
PS2QueuePush(MousePS2Port, MOUSE_ACK);
break;
}
/* Set 2:1 Scaling */
case 0xE7:
{
Scaling = TRUE;
PS2QueuePush(MousePS2Port, MOUSE_ACK);
break;
}
/* Set Resolution */
case 0xE8:
/* Set Sample Rate */
case 0xF3:
{
MouseDataByteWait = Command;
PS2QueuePush(MousePS2Port, MOUSE_ACK);
break;
}
/* Read Status */
case 0xE9:
{
BYTE Status = ButtonState & 7;
if (Scaling) Status |= 1 << 4;
if (MouseReporting) Status |= 1 << 5;
if (Mode == MOUSE_REMOTE_MODE) Status |= 1 << 6;
PS2QueuePush(MousePS2Port, MOUSE_ACK);
PS2QueuePush(MousePS2Port, Status);
PS2QueuePush(MousePS2Port, Resolution);
PS2QueuePush(MousePS2Port, (BYTE)(1000 / MouseCycles));
break;
}
/* Enter Streaming Mode */
case 0xEA:
{
MouseResetCounters();
Mode = MOUSE_STREAMING_MODE;
PS2QueuePush(MousePS2Port, MOUSE_ACK);
break;
}
/* Read Packet */
case 0xEB:
{
PS2QueuePush(MousePS2Port, MOUSE_ACK);
MouseGetPacket(&LastPacket);
MouseDispatchPacket(&LastPacket);
break;
}
/* Return from Wrap Mode */
case 0xEC:
{
if (Mode == MOUSE_WRAP_MODE)
{
/* Restore the previous mode */
MouseResetCounters();
Mode = PreviousMode;
PS2QueuePush(MousePS2Port, MOUSE_ACK);
}
else
{
PS2QueuePush(MousePS2Port, MOUSE_ERROR);
}
break;
}
/* Enter Wrap Mode */
case 0xEE:
{
if (Mode != MOUSE_WRAP_MODE)
{
/* Save the previous mode */
PreviousMode = Mode;
}
MouseResetCounters();
Mode = MOUSE_WRAP_MODE;
PS2QueuePush(MousePS2Port, MOUSE_ACK);
break;
}
/* Enter Remote Mode */
case 0xF0:
{
MouseResetCounters();
Mode = MOUSE_REMOTE_MODE;
PS2QueuePush(MousePS2Port, MOUSE_ACK);
break;
}
/* Get Mouse ID */
case 0xF2:
{
PS2QueuePush(MousePS2Port, MOUSE_ACK);
PS2QueuePush(MousePS2Port, MouseId);
break;
}
/* Enable Reporting */
case 0xF4:
{
MouseReporting = TRUE;
MouseResetCounters();
PS2QueuePush(MousePS2Port, MOUSE_ACK);
break;
}
/* Disable Reporting */
case 0xF5:
{
MouseReporting = FALSE;
MouseResetCounters();
PS2QueuePush(MousePS2Port, MOUSE_ACK);
break;
}
/* Set Defaults */
case 0xF6:
{
/* Reset the configuration and counters */
MouseResetConfig();
MouseResetCounters();
PS2QueuePush(MousePS2Port, MOUSE_ACK);
break;
}
/* Resend */
case 0xFE:
{
PS2QueuePush(MousePS2Port, MOUSE_ACK);
MouseDispatchPacket(&LastPacket);
break;
}
/* Reset */
case 0xFF:
{
/* Send ACKnowledge */
PS2QueuePush(MousePS2Port, MOUSE_ACK);
MouseReset();
/* Send the Basic Assurance Test success code and the device ID */
PS2QueuePush(MousePS2Port, MOUSE_BAT_SUCCESS);
PS2QueuePush(MousePS2Port, MouseId);
break;
}
/* Unknown command */
default:
{
PS2QueuePush(MousePS2Port, MOUSE_ERROR);
}
}
}
static VOID FASTCALL MouseStreamingCallback(ULONGLONG ElapsedTime)
{
UNREFERENCED_PARAMETER(ElapsedTime);
/* Check if we're not in streaming mode, not reporting, or there's nothing to report */
if (Mode != MOUSE_STREAMING_MODE || !MouseReporting || !EventsOccurred) return;
MouseGetPacket(&LastPacket);
MouseDispatchPacket(&LastPacket);
EventsOccurred = FALSE;
}
/* PUBLIC FUNCTIONS ***********************************************************/
VOID MouseGetDataFast(PCOORD CurrentPosition, PBYTE CurrentButtonState)
{
WaitForSingleObject(MouseMutex, INFINITE);
*CurrentPosition = Position;
*CurrentButtonState = LOBYTE(ButtonState);
ReleaseMutex(MouseMutex);
}
VOID MouseEventHandler(PMOUSE_EVENT_RECORD MouseEvent)
{
COORD NewPosition = MouseEvent->dwMousePosition;
BOOLEAN DoubleWidth = FALSE, DoubleHeight = FALSE;
if (!VgaGetDoubleVisionState(&DoubleWidth, &DoubleHeight))
{
/* Text mode */
NewPosition.X *= 8;
NewPosition.Y *= 8;
}
/* Adjust for double vision */
if (DoubleWidth) NewPosition.X /= 2;
if (DoubleHeight) NewPosition.Y /= 2;
WaitForSingleObject(MouseMutex, INFINITE);
/* Update the counters */
HorzCounter += (NewPosition.X - Position.X) << DoubleWidth;
VertCounter += (NewPosition.Y - Position.Y) << DoubleHeight;
/* Update the position */
Position = NewPosition;
/* Update the button state */
ButtonState = MouseEvent->dwButtonState;
if (MouseEvent->dwEventFlags & MOUSE_WHEELED)
{
ScrollCounter += (SHORT)HIWORD(MouseEvent->dwButtonState);
}
EventsOccurred = TRUE;
ReleaseMutex(MouseMutex);
}
BOOLEAN MouseInit(BYTE PS2Connector)
{
/* Finish to plug the mouse to the specified PS/2 port */
MousePS2Port = PS2Connector;
PS2SetDeviceCmdProc(MousePS2Port, NULL, MouseCommand);
MouseMutex = CreateMutex(NULL, FALSE, NULL);
if (MouseMutex == NULL) return FALSE;
StreamTimer = CreateHardwareTimer(HARDWARE_TIMER_ENABLED,
HZ_TO_NS(100),
MouseStreamingCallback);
MouseReset();
return TRUE;
}