[NTVDM]
[reactos.git] / reactos / subsystems / mvdm / ntvdm / hardware / mouse.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: mouse.c
5 * PURPOSE: Mouse emulation
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #define NDEBUG
12
13 #include "mouse.h"
14 #include "ps2.h"
15 #include "clock.h"
16 #include "video/vga.h"
17
18 /* PRIVATE VARIABLES **********************************************************/
19
20 static const BYTE ScrollMagic[3] = { 200, 100, 80 };
21 static const BYTE ExtraButtonMagic[3] = { 200, 200, 80 };
22
23 static HANDLE MouseMutex;
24 static PHARDWARE_TIMER StreamTimer;
25 static MOUSE_PACKET LastPacket;
26 static MOUSE_MODE Mode, PreviousMode;
27 static COORD Position;
28 static BYTE Resolution; /* Completely ignored */
29 static BOOLEAN Scaling; /* Completely ignored */
30 static BOOLEAN Reporting;
31 static BYTE MouseId;
32 static ULONG ButtonState;
33 static SHORT HorzCounter;
34 static SHORT VertCounter;
35 static CHAR ScrollCounter;
36 static BOOLEAN EventsOccurred = FALSE;
37 static BYTE DataByteWait = 0;
38 static BYTE ScrollMagicCounter = 0, ExtraButtonMagicCounter = 0;
39
40 static BYTE PS2Port = 1;
41
42 /* PUBLIC VARIABLES ***********************************************************/
43
44 UINT MouseCycles = 10;
45
46 /* PRIVATE FUNCTIONS **********************************************************/
47
48 static VOID MouseResetConfig(VOID)
49 {
50 /* Reset the configuration to defaults */
51 MouseCycles = 10;
52 Resolution = 4;
53 Scaling = FALSE;
54 Reporting = FALSE;
55 }
56
57 static VOID MouseResetCounters(VOID)
58 {
59 /* Reset all flags and counters */
60 HorzCounter = VertCounter = ScrollCounter = 0;
61 }
62
63 static VOID MouseReset(VOID)
64 {
65 /* Reset everything */
66 MouseResetConfig();
67 MouseResetCounters();
68
69 /* Enter streaming mode and the reset the mouse ID */
70 Mode = MOUSE_STREAMING_MODE;
71 MouseId = 0;
72 ScrollMagicCounter = ExtraButtonMagicCounter = 0;
73
74 /* Send the Basic Assurance Test success code and the device ID */
75 PS2QueuePush(PS2Port, MOUSE_BAT_SUCCESS);
76 PS2QueuePush(PS2Port, MouseId);
77 }
78
79 static VOID MouseGetPacket(PMOUSE_PACKET Packet)
80 {
81 /* Clear the packet */
82 RtlZeroMemory(Packet, sizeof(*Packet));
83
84 /* Acquire the mutex */
85 WaitForSingleObject(MouseMutex, INFINITE);
86
87 Packet->Flags |= MOUSE_ALWAYS_SET;
88
89 /* Set the sign flags */
90 if (HorzCounter < 0)
91 {
92 Packet->Flags |= MOUSE_X_SIGN;
93 HorzCounter = -HorzCounter;
94 }
95
96 if (VertCounter < 0)
97 {
98 Packet->Flags |= MOUSE_Y_SIGN;
99 VertCounter = -VertCounter;
100 }
101
102 /* Check for horizontal overflows */
103 if (HorzCounter > MOUSE_MAX)
104 {
105 HorzCounter = MOUSE_MAX;
106 Packet->Flags |= MOUSE_X_OVERFLOW;
107 }
108
109 /* Check for vertical overflows */
110 if (VertCounter > MOUSE_MAX)
111 {
112 VertCounter = MOUSE_MAX;
113 Packet->Flags |= MOUSE_Y_OVERFLOW;
114 }
115
116 /* Set the button flags */
117 if (ButtonState & FROM_LEFT_1ST_BUTTON_PRESSED) Packet->Flags |= MOUSE_LEFT_BUTTON;
118 if (ButtonState & FROM_LEFT_2ND_BUTTON_PRESSED) Packet->Flags |= MOUSE_MIDDLE_BUTTON;
119 if (ButtonState & RIGHTMOST_BUTTON_PRESSED) Packet->Flags |= MOUSE_RIGHT_BUTTON;
120
121 if (MouseId == 4)
122 {
123 if (ButtonState & FROM_LEFT_3RD_BUTTON_PRESSED) Packet->Extra |= MOUSE_4TH_BUTTON;
124 if (ButtonState & FROM_LEFT_4TH_BUTTON_PRESSED) Packet->Extra |= MOUSE_5TH_BUTTON;
125 }
126
127 if (MouseId >= 3)
128 {
129 /* Set the scroll counter */
130 Packet->Extra |= (UCHAR)ScrollCounter & 0x0F;
131 }
132
133 /* Store the counters in the packet */
134 Packet->HorzCounter = LOBYTE(HorzCounter);
135 Packet->VertCounter = LOBYTE(VertCounter);
136
137 /* Reset the counters */
138 MouseResetCounters();
139
140 /* Release the mutex */
141 ReleaseMutex(MouseMutex);
142 }
143
144 static VOID MouseDispatchPacket(PMOUSE_PACKET Packet)
145 {
146 PS2QueuePush(PS2Port, Packet->Flags);
147 PS2QueuePush(PS2Port, Packet->HorzCounter);
148 PS2QueuePush(PS2Port, Packet->VertCounter);
149 if (MouseId >= 3) PS2QueuePush(PS2Port, Packet->Extra);
150 }
151
152 static VOID WINAPI MouseCommand(LPVOID Param, BYTE Command)
153 {
154 /* Check if we were waiting for a data byte */
155 if (DataByteWait)
156 {
157 PS2QueuePush(PS2Port, MOUSE_ACK);
158
159 switch (DataByteWait)
160 {
161 /* Set Resolution */
162 case 0xE8:
163 {
164 Resolution = Command;
165 break;
166 }
167
168 /* Set Sample Rate */
169 case 0xF3:
170 {
171 /* Check for the scroll wheel enabling sequence */
172 if (MouseId == 0)
173 {
174 if (Command == ScrollMagic[ScrollMagicCounter])
175 {
176 ScrollMagicCounter++;
177 if (ScrollMagicCounter == 3) MouseId = 3;
178 }
179 else
180 {
181 ScrollMagicCounter = 0;
182 }
183 }
184
185 /* Check for the 5-button enabling sequence */
186 if (MouseId == 3)
187 {
188 if (Command == ExtraButtonMagic[ExtraButtonMagicCounter])
189 {
190 ExtraButtonMagicCounter++;
191 if (ExtraButtonMagicCounter == 3) MouseId = 4;
192 }
193 else
194 {
195 ExtraButtonMagicCounter = 0;
196 }
197 }
198
199 MouseCycles = 1000 / (UINT)Command;
200 break;
201 }
202
203 default:
204 {
205 /* Shouldn't happen */
206 ASSERT(FALSE);
207 }
208 }
209
210 DataByteWait = 0;
211 }
212
213 /* Check if we're in wrap mode */
214 if (Mode == MOUSE_WRAP_MODE)
215 {
216 /*
217 * In this mode, we just echo whatever byte we get,
218 * except for the 0xEC and 0xFF commands.
219 */
220 if (Command != 0xEC && Command != 0xFF)
221 {
222 PS2QueuePush(PS2Port, Command);
223 return;
224 }
225 }
226
227 switch (Command)
228 {
229 /* Set 1:1 Scaling */
230 case 0xE6:
231 {
232 Scaling = FALSE;
233 PS2QueuePush(PS2Port, MOUSE_ACK);
234 break;
235 }
236
237 /* Set 2:1 Scaling */
238 case 0xE7:
239 {
240 Scaling = TRUE;
241 PS2QueuePush(PS2Port, MOUSE_ACK);
242 break;
243 }
244
245 /* Set Resolution */
246 case 0xE8:
247 /* Set Sample Rate */
248 case 0xF3:
249 {
250 PS2QueuePush(PS2Port, MOUSE_ACK);
251 DataByteWait = Command;
252 break;
253 }
254
255 /* Read Status */
256 case 0xE9:
257 {
258 BYTE Status = ButtonState & 7;
259 PS2QueuePush(PS2Port, MOUSE_ACK);
260
261 if (Scaling) Status |= 1 << 4;
262 if (Reporting) Status |= 1 << 5;
263 if (Mode == MOUSE_REMOTE_MODE) Status |= 1 << 6;
264
265 PS2QueuePush(PS2Port, Status);
266 PS2QueuePush(PS2Port, Resolution);
267 PS2QueuePush(PS2Port, (BYTE)(1000 / MouseCycles));
268 break;
269 }
270
271 /* Enter Streaming Mode */
272 case 0xEA:
273 {
274 MouseResetCounters();
275 Mode = MOUSE_STREAMING_MODE;
276
277 PS2QueuePush(PS2Port, MOUSE_ACK);
278 break;
279 }
280
281 /* Read Packet */
282 case 0xEB:
283 {
284 PS2QueuePush(PS2Port, MOUSE_ACK);
285 MouseGetPacket(&LastPacket);
286 MouseDispatchPacket(&LastPacket);
287 break;
288 }
289
290 /* Return From Wrap Mode */
291 case 0xEC:
292 {
293 if (Mode == MOUSE_WRAP_MODE)
294 {
295 /* Restore the previous mode */
296 MouseResetCounters();
297 Mode = PreviousMode;
298 PS2QueuePush(PS2Port, MOUSE_ACK);
299 }
300 else PS2QueuePush(PS2Port, MOUSE_ERROR);
301
302 break;
303 }
304
305 /* Enter Wrap Mode */
306 case 0xEE:
307 {
308 if (Mode != MOUSE_WRAP_MODE)
309 {
310 /* Save the previous mode */
311 PreviousMode = Mode;
312 }
313
314 MouseResetCounters();
315 Mode = MOUSE_WRAP_MODE;
316
317 PS2QueuePush(PS2Port, MOUSE_ACK);
318 break;
319 }
320
321 /* Enter Remote Mode */
322 case 0xF0:
323 {
324 MouseResetCounters();
325 Mode = MOUSE_REMOTE_MODE;
326
327 PS2QueuePush(PS2Port, MOUSE_ACK);
328 break;
329 }
330
331 /* Get Mouse ID */
332 case 0xF2:
333 {
334 PS2QueuePush(PS2Port, MOUSE_ACK);
335 PS2QueuePush(PS2Port, MouseId);
336 break;
337 }
338
339 /* Enable Reporting */
340 case 0xF4:
341 {
342 Reporting = TRUE;
343 PS2QueuePush(PS2Port, MOUSE_ACK);
344 break;
345 }
346
347 /* Disable Reporting */
348 case 0xF5:
349 {
350 Reporting = FALSE;
351 PS2QueuePush(PS2Port, MOUSE_ACK);
352 break;
353 }
354
355 /* Set Defaults */
356 case 0xF6:
357 {
358 /* Reset the configuration and counters */
359 MouseResetConfig();
360 MouseResetCounters();
361 break;
362 }
363
364 /* Resend */
365 case 0xFE:
366 {
367 PS2QueuePush(PS2Port, MOUSE_ACK);
368 MouseDispatchPacket(&LastPacket);
369 break;
370 }
371
372 /* Reset */
373 case 0xFF:
374 {
375 MouseReset();
376 break;
377 }
378
379 /* Unknown command */
380 default:
381 {
382 PS2QueuePush(PS2Port, MOUSE_ERROR);
383 }
384 }
385 }
386
387 static VOID FASTCALL MouseStreamingCallback(ULONGLONG ElapsedTime)
388 {
389 UNREFERENCED_PARAMETER(ElapsedTime);
390
391 /* Check if we're not in streaming mode, not reporting, or there's nothing to report */
392 if (Mode != MOUSE_STREAMING_MODE || !Reporting || !EventsOccurred) return;
393
394 MouseGetPacket(&LastPacket);
395 MouseDispatchPacket(&LastPacket);
396
397 EventsOccurred = FALSE;
398 }
399
400 /* PUBLIC FUNCTIONS ***********************************************************/
401
402 VOID MouseGetDataFast(PCOORD CurrentPosition, PBYTE CurrentButtonState)
403 {
404 WaitForSingleObject(MouseMutex, INFINITE);
405 *CurrentPosition = Position;
406 *CurrentButtonState = LOBYTE(ButtonState);
407 ReleaseMutex(MouseMutex);
408 }
409
410 VOID MouseEventHandler(PMOUSE_EVENT_RECORD MouseEvent)
411 {
412 COORD NewPosition = MouseEvent->dwMousePosition;
413 BOOLEAN DoubleWidth = FALSE, DoubleHeight = FALSE;
414
415 if (!VgaGetDoubleVisionState(&DoubleWidth, &DoubleHeight))
416 {
417 /* Text mode */
418 NewPosition.X *= 8;
419 NewPosition.Y *= 8;
420 }
421
422 /* Adjust for double vision */
423 if (DoubleWidth) NewPosition.X /= 2;
424 if (DoubleHeight) NewPosition.Y /= 2;
425
426 WaitForSingleObject(MouseMutex, INFINITE);
427
428 /* Update the counters */
429 HorzCounter += NewPosition.X - Position.X;
430 VertCounter += NewPosition.Y - Position.Y;
431
432 /* Update the position */
433 Position = NewPosition;
434
435 /* Update the button state */
436 ButtonState = MouseEvent->dwButtonState;
437
438 if (MouseEvent->dwEventFlags & MOUSE_WHEELED)
439 {
440 ScrollCounter += (SHORT)HIWORD(MouseEvent->dwButtonState);
441 }
442
443 EventsOccurred = TRUE;
444 ReleaseMutex(MouseMutex);
445 }
446
447 BOOLEAN MouseInit(BYTE PS2Connector)
448 {
449 /* Finish to plug the mouse to the specified PS/2 port */
450 PS2Port = PS2Connector;
451 PS2SetDeviceCmdProc(PS2Port, NULL, MouseCommand);
452
453 MouseMutex = CreateMutex(NULL, FALSE, NULL);
454 if (MouseMutex == NULL) return FALSE;
455
456 StreamTimer = CreateHardwareTimer(HARDWARE_TIMER_ENABLED, 10, MouseStreamingCallback);
457
458 MouseReset();
459 return TRUE;
460 }