[NTVDM]: For CreateHardwareTimer, use frequencies (in hertz) instead of using millise...
[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 return;
212 }
213
214 /* Check if we're in wrap mode */
215 if (Mode == MOUSE_WRAP_MODE)
216 {
217 /*
218 * In this mode, we just echo whatever byte we get,
219 * except for the 0xEC and 0xFF commands.
220 */
221 if (Command != 0xEC && Command != 0xFF)
222 {
223 PS2QueuePush(PS2Port, Command);
224 return;
225 }
226 }
227
228 switch (Command)
229 {
230 /* Set 1:1 Scaling */
231 case 0xE6:
232 {
233 Scaling = FALSE;
234 PS2QueuePush(PS2Port, MOUSE_ACK);
235 break;
236 }
237
238 /* Set 2:1 Scaling */
239 case 0xE7:
240 {
241 Scaling = TRUE;
242 PS2QueuePush(PS2Port, MOUSE_ACK);
243 break;
244 }
245
246 /* Set Resolution */
247 case 0xE8:
248 /* Set Sample Rate */
249 case 0xF3:
250 {
251 PS2QueuePush(PS2Port, MOUSE_ACK);
252 DataByteWait = Command;
253 break;
254 }
255
256 /* Read Status */
257 case 0xE9:
258 {
259 BYTE Status = ButtonState & 7;
260 PS2QueuePush(PS2Port, MOUSE_ACK);
261
262 if (Scaling) Status |= 1 << 4;
263 if (Reporting) Status |= 1 << 5;
264 if (Mode == MOUSE_REMOTE_MODE) Status |= 1 << 6;
265
266 PS2QueuePush(PS2Port, Status);
267 PS2QueuePush(PS2Port, Resolution);
268 PS2QueuePush(PS2Port, (BYTE)(1000 / MouseCycles));
269 break;
270 }
271
272 /* Enter Streaming Mode */
273 case 0xEA:
274 {
275 MouseResetCounters();
276 Mode = MOUSE_STREAMING_MODE;
277
278 PS2QueuePush(PS2Port, MOUSE_ACK);
279 break;
280 }
281
282 /* Read Packet */
283 case 0xEB:
284 {
285 PS2QueuePush(PS2Port, MOUSE_ACK);
286 MouseGetPacket(&LastPacket);
287 MouseDispatchPacket(&LastPacket);
288 break;
289 }
290
291 /* Return From Wrap Mode */
292 case 0xEC:
293 {
294 if (Mode == MOUSE_WRAP_MODE)
295 {
296 /* Restore the previous mode */
297 MouseResetCounters();
298 Mode = PreviousMode;
299 PS2QueuePush(PS2Port, MOUSE_ACK);
300 }
301 else PS2QueuePush(PS2Port, MOUSE_ERROR);
302
303 break;
304 }
305
306 /* Enter Wrap Mode */
307 case 0xEE:
308 {
309 if (Mode != MOUSE_WRAP_MODE)
310 {
311 /* Save the previous mode */
312 PreviousMode = Mode;
313 }
314
315 MouseResetCounters();
316 Mode = MOUSE_WRAP_MODE;
317
318 PS2QueuePush(PS2Port, MOUSE_ACK);
319 break;
320 }
321
322 /* Enter Remote Mode */
323 case 0xF0:
324 {
325 MouseResetCounters();
326 Mode = MOUSE_REMOTE_MODE;
327
328 PS2QueuePush(PS2Port, MOUSE_ACK);
329 break;
330 }
331
332 /* Get Mouse ID */
333 case 0xF2:
334 {
335 PS2QueuePush(PS2Port, MOUSE_ACK);
336 PS2QueuePush(PS2Port, MouseId);
337 break;
338 }
339
340 /* Enable Reporting */
341 case 0xF4:
342 {
343 Reporting = TRUE;
344 PS2QueuePush(PS2Port, MOUSE_ACK);
345 break;
346 }
347
348 /* Disable Reporting */
349 case 0xF5:
350 {
351 Reporting = FALSE;
352 PS2QueuePush(PS2Port, MOUSE_ACK);
353 break;
354 }
355
356 /* Set Defaults */
357 case 0xF6:
358 {
359 /* Reset the configuration and counters */
360 MouseResetConfig();
361 MouseResetCounters();
362 break;
363 }
364
365 /* Resend */
366 case 0xFE:
367 {
368 PS2QueuePush(PS2Port, MOUSE_ACK);
369 MouseDispatchPacket(&LastPacket);
370 break;
371 }
372
373 /* Reset */
374 case 0xFF:
375 {
376 MouseReset();
377 break;
378 }
379
380 /* Unknown command */
381 default:
382 {
383 PS2QueuePush(PS2Port, MOUSE_ERROR);
384 }
385 }
386 }
387
388 static VOID FASTCALL MouseStreamingCallback(ULONGLONG ElapsedTime)
389 {
390 UNREFERENCED_PARAMETER(ElapsedTime);
391
392 /* Check if we're not in streaming mode, not reporting, or there's nothing to report */
393 if (Mode != MOUSE_STREAMING_MODE || !Reporting || !EventsOccurred) return;
394
395 MouseGetPacket(&LastPacket);
396 MouseDispatchPacket(&LastPacket);
397
398 EventsOccurred = FALSE;
399 }
400
401 /* PUBLIC FUNCTIONS ***********************************************************/
402
403 VOID MouseGetDataFast(PCOORD CurrentPosition, PBYTE CurrentButtonState)
404 {
405 WaitForSingleObject(MouseMutex, INFINITE);
406 *CurrentPosition = Position;
407 *CurrentButtonState = LOBYTE(ButtonState);
408 ReleaseMutex(MouseMutex);
409 }
410
411 VOID MouseEventHandler(PMOUSE_EVENT_RECORD MouseEvent)
412 {
413 COORD NewPosition = MouseEvent->dwMousePosition;
414 BOOLEAN DoubleWidth = FALSE, DoubleHeight = FALSE;
415
416 if (!VgaGetDoubleVisionState(&DoubleWidth, &DoubleHeight))
417 {
418 /* Text mode */
419 NewPosition.X *= 8;
420 NewPosition.Y *= 8;
421 }
422
423 /* Adjust for double vision */
424 if (DoubleWidth) NewPosition.X /= 2;
425 if (DoubleHeight) NewPosition.Y /= 2;
426
427 WaitForSingleObject(MouseMutex, INFINITE);
428
429 /* Update the counters */
430 HorzCounter += NewPosition.X - Position.X;
431 VertCounter += NewPosition.Y - Position.Y;
432
433 /* Update the position */
434 Position = NewPosition;
435
436 /* Update the button state */
437 ButtonState = MouseEvent->dwButtonState;
438
439 if (MouseEvent->dwEventFlags & MOUSE_WHEELED)
440 {
441 ScrollCounter += (SHORT)HIWORD(MouseEvent->dwButtonState);
442 }
443
444 EventsOccurred = TRUE;
445 ReleaseMutex(MouseMutex);
446 }
447
448 BOOLEAN MouseInit(BYTE PS2Connector)
449 {
450 /* Finish to plug the mouse to the specified PS/2 port */
451 PS2Port = PS2Connector;
452 PS2SetDeviceCmdProc(PS2Port, NULL, MouseCommand);
453
454 MouseMutex = CreateMutex(NULL, FALSE, NULL);
455 if (MouseMutex == NULL) return FALSE;
456
457 StreamTimer = CreateHardwareTimer(HARDWARE_TIMER_ENABLED, 100, MouseStreamingCallback);
458
459 MouseReset();
460 return TRUE;
461 }