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