xref: /reactos/subsystems/mvdm/ntvdm/dos/mouse32.c (revision 8540ab04)
1 /*
2  * COPYRIGHT:       GPL - See COPYING in the top level directory
3  * PROJECT:         ReactOS Virtual DOS Machine
4  * FILE:            subsystems/mvdm/ntvdm/dos/mouse32.c
5  * PURPOSE:         VDM 32-bit compatible PS/2 MOUSE.COM driver
6  * PROGRAMMERS:     Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7  */
8 
9 /* INCLUDES *******************************************************************/
10 
11 #include "ntvdm.h"
12 
13 #define NDEBUG
14 #include <debug.h>
15 
16 /* Driver Version number and Copyright */
17 #include <reactos/buildno.h>
18 #include <reactos/version.h>
19 
20 #include "emulator.h"
21 
22 #include "cpu/cpu.h"
23 #include "int32.h"
24 #include "hardware/mouse.h"
25 #include "hardware/ps2.h"
26 #include "hardware/pic.h"
27 #include "hardware/video/svga.h"
28 /**/
29 #include "../console/video.h"
30 /**/
31 
32 #include "mouse32.h"
33 #include "bios/bios.h"
34 #include "bios/bios32/bios32p.h"
35 
36 #include "memory.h"
37 #include "io.h"
38 #include "dos32krnl/memory.h"
39 
40 /* PRIVATE VARIABLES **********************************************************/
41 
42 static const CHAR MouseCopyright[] =
43     "ReactOS PS/2 16/32-bit Mouse Driver Compatible MS-MOUSE 6.26\r\n"
44     "Version "KERNEL_VERSION_STR" (Build "KERNEL_VERSION_BUILD_STR")\r\n"
45     "Copyright (C) ReactOS Team 1996-"COPYRIGHT_YEAR"\0";
46 
47 #pragma pack(push, 1)
48 
49 typedef struct _MOUSE_DRIVER
50 {
51     CHAR Copyright[sizeof(MouseCopyright)];
52     WORD Version;
53     BYTE MouseContextScratch[TRAMPOLINE_SIZE];
54     BYTE MouseDosInt16Stub[Int16To32StubSize];
55     BYTE MouseIrqInt16Stub[Int16To32StubSize];
56 } MOUSE_DRIVER, *PMOUSE_DRIVER;
57 
58 #pragma pack(pop)
59 
60 /* Global data contained in guest memory */
61 static WORD MouseDataSegment;
62 static PMOUSE_DRIVER MouseData;
63 static CALLBACK16 MouseContext;
64 
65 #define MICKEYS_PER_CELL_HORIZ  8
66 #define MICKEYS_PER_CELL_VERT   16
67 
68 static BOOLEAN DriverEnabled = FALSE;
69 static MOUSE_DRIVER_STATE DriverState;
70 static DWORD OldIrqHandler;
71 static DWORD OldIntHandler;
72 
73 static WORD DefaultGfxScreenMask[16] =
74 {
75     0xE7FF,     // 1110011111111111
76     0xE3FF,     // 1110001111111111
77     0xE1FF,     // 1110000111111111
78     0xE0FF,     // 1110000011111111
79     0xE07F,     // 1110000001111111
80     0xE03F,     // 1110000000111111
81     0xE01F,     // 1110000000011111
82     0xE00F,     // 1110000000001111
83     0xE007,     // 1110000000000111
84     0xE007,     // 1110000000000111
85     0xE03F,     // 1110000000111111
86     0xE21F,     // 1110001000011111
87     0xE61F,     // 1110011000011111
88     0xFF0F,     // 1111111100001111
89     0xFF0F,     // 1111111100001111
90     0xFF8F,     // 1111111110001111
91 };
92 
93 static WORD DefaultGfxCursorMask[16] =
94 {
95     0x0000,     // 0000000000000000
96     0x0800,     // 0000100000000000
97     0x0C00,     // 0000110000000000
98     0x0E00,     // 0000111000000000
99     0x0F00,     // 0000111100000000
100     0x0F80,     // 0000111110000000
101     0x0FC0,     // 0000111111000000
102     0x0FE0,     // 0000111111100000
103     0x0FF0,     // 0000111111110000
104     0x0F80,     // 0000111110000000
105     0x0D80,     // 0000110110000000
106     0x08C0,     // 0000100011000000
107     0x00C0,     // 0000000011000000
108     0x0060,     // 0000000001100000
109     0x0060,     // 0000000001100000
110     0x0000,     // 0000000000000000
111 };
112 
113 /* PRIVATE FUNCTIONS **********************************************************/
114 
115 /* static */
116 VOID BiosPs2Service(UCHAR Function)
117 {
118     /* Save AX and BX */
119     USHORT AX = getAX();
120     // USHORT BX = getBX();
121 
122     /*
123      * Set the parameters:
124      * AL contains the character to print (already set),
125      * BL contains the character attribute,
126      * BH contains the video page to use.
127      */
128     // setBL(DOS_CHAR_ATTRIBUTE);
129     // setBH(Bda->VideoPage);
130     setAL(Function);
131 
132     /* Call the BIOS INT 15h, AH=C2h "Pointing Device BIOS Interface (PS)" */
133     setAH(0xC2);
134     Int32Call(&MouseContext, BIOS_MISC_INTERRUPT);
135 
136     /* Restore AX and BX */
137     // setBX(BX);
138     setAX(AX);
139 }
140 
141 
142 
143 static VOID DosMouseEnable(VOID);
144 static VOID DosMouseDisable(VOID);
145 
146 
147 static VOID PaintMouseCursor(VOID)
148 {
149     if (Bda->VideoMode <= 3)
150     {
151         WORD Character;
152         WORD CellX = DriverState.Position.X / 8;
153         WORD CellY = DriverState.Position.Y / 8;
154         DWORD VideoAddress = TO_LINEAR(TEXT_VIDEO_SEG, Bda->VideoPage * Bda->VideoPageSize);
155 
156         EmulatorReadMemory(&EmulatorContext,
157                            VideoAddress
158                            + (CellY * Bda->ScreenColumns + CellX) * sizeof(WORD),
159                            (LPVOID)&Character,
160                            sizeof(WORD));
161 
162         DriverState.Character = Character;
163         Character &= DriverState.TextCursor.ScreenMask;
164         Character ^= DriverState.TextCursor.CursorMask;
165 
166         EmulatorWriteMemory(&EmulatorContext,
167                             VideoAddress
168                             + (CellY * Bda->ScreenColumns + CellX) * sizeof(WORD),
169                             (LPVOID)&Character,
170                             sizeof(WORD));
171     }
172     else if (Bda->VideoMode == 0x12)
173     {
174         INT i, j;
175         BYTE OldMask;
176         BYTE OldMap;
177 
178         /* Save the write mask */
179         IOWriteB(VGA_SEQ_INDEX, VGA_SEQ_MASK_REG);
180         OldMask = IOReadB(VGA_SEQ_DATA);
181 
182         /* And the selected reading plane */
183         IOWriteB(VGA_GC_INDEX, VGA_GC_READ_MAP_SEL_REG);
184         OldMap = IOReadB(VGA_GC_DATA);
185 
186         for (i = 0; i < 16; i++)
187         {
188             WORD CursorLine[4];
189             DWORD VideoAddress = TO_LINEAR(GRAPHICS_VIDEO_SEG, Bda->VideoPage * Bda->VideoPageSize)
190                                  + ((DriverState.Position.Y + i) * 640 + DriverState.Position.X) / 8;
191 
192             for (j = 0; j < 4; j++)
193             {
194                 /* Select the reading plane */
195                 IOWriteB(VGA_GC_INDEX, VGA_GC_READ_MAP_SEL_REG);
196                 IOWriteB(VGA_GC_DATA, j);
197 
198                 /* Read a part of the scanline */
199                 EmulatorReadMemory(&EmulatorContext, VideoAddress, &CursorLine[j], sizeof(CursorLine[j]));
200             }
201 
202             /* Save the data below the cursor */
203             for (j = 0; j < 16; j++)
204             {
205                 DriverState.GraphicsData[i * 16 + j] = 0;
206 
207                 if (CursorLine[0] & (1 << j)) DriverState.GraphicsData[i * 16 + j] |= 1 << 0;
208                 if (CursorLine[1] & (1 << j)) DriverState.GraphicsData[i * 16 + j] |= 1 << 1;
209                 if (CursorLine[2] & (1 << j)) DriverState.GraphicsData[i * 16 + j] |= 1 << 2;
210                 if (CursorLine[3] & (1 << j)) DriverState.GraphicsData[i * 16 + j] |= 1 << 3;
211             }
212 
213             for (j = 0; j < 4; j++)
214             {
215                 /* Apply the screen mask */
216                 CursorLine[j] &= MAKEWORD(HIBYTE(DriverState.GraphicsCursor.ScreenMask[i]),
217                                           LOBYTE(DriverState.GraphicsCursor.ScreenMask[i]));
218 
219                 /* And the cursor mask */
220                 CursorLine[j] ^= MAKEWORD(HIBYTE(DriverState.GraphicsCursor.CursorMask[i]),
221                                           LOBYTE(DriverState.GraphicsCursor.CursorMask[i]));
222 
223                 /* Select the writing plane */
224                 IOWriteB(VGA_SEQ_INDEX, VGA_SEQ_MASK_REG);
225                 IOWriteB(VGA_SEQ_DATA, 1 << j);
226 
227                 /* Write the cursor data for this scanline */
228                 EmulatorWriteMemory(&EmulatorContext, VideoAddress, &CursorLine[j], sizeof(CursorLine[j]));
229             }
230         }
231 
232         /* Restore the old mask */
233         IOWriteB(VGA_SEQ_INDEX, VGA_SEQ_MASK_REG);
234         IOWriteB(VGA_SEQ_DATA, OldMask);
235 
236         /* And the old reading plane */
237         IOWriteB(VGA_GC_INDEX, VGA_GC_READ_MAP_SEL_REG);
238         IOWriteB(VGA_GC_DATA, OldMap);
239     }
240     else if (Bda->VideoMode == 0x13)
241     {
242         INT i, j;
243 
244         for (i = 0; i < 16; i++)
245         {
246             BYTE CursorLine[16];
247             DWORD VideoAddress = TO_LINEAR(GRAPHICS_VIDEO_SEG, Bda->VideoPage * Bda->VideoPageSize)
248                                  + (DriverState.Position.Y + i) * 320 + DriverState.Position.X;
249 
250             /* Read a part of the scanline */
251             EmulatorReadMemory(&EmulatorContext,
252                                VideoAddress,
253                                &DriverState.GraphicsData[i * 16],
254                                sizeof(CursorLine));
255 
256             for (j = 0; j < 16; j++)
257             {
258                 /* Apply the screen mask by leaving only the masked pixels intact */
259                 CursorLine[j] = (DriverState.GraphicsCursor.ScreenMask[i] & (1 << j))
260                                 ? DriverState.GraphicsData[i * 16]
261                                 : 0x00;
262 
263                 /* Apply the cursor mask... */
264                 if (DriverState.GraphicsCursor.CursorMask[i] & (1 << j))
265                 {
266                     /* ... by inverting the color of each masked pixel */
267                     CursorLine[j] ^= 0x0F;
268                 }
269             }
270 
271             /* Write the cursor data for this scanline */
272             EmulatorWriteMemory(&EmulatorContext, VideoAddress, &CursorLine, sizeof(CursorLine));
273         }
274     }
275     else
276     {
277         // TODO: NOT IMPLEMENTED
278         UNIMPLEMENTED;
279     }
280 }
281 
282 static VOID EraseMouseCursor(VOID)
283 {
284     if (Bda->VideoMode <= 3)
285     {
286         WORD CellX = DriverState.Position.X / 8;
287         WORD CellY = DriverState.Position.Y / 8;
288         DWORD VideoAddress = TO_LINEAR(TEXT_VIDEO_SEG, Bda->VideoPage * Bda->VideoPageSize);
289 
290         EmulatorWriteMemory(&EmulatorContext,
291                             VideoAddress
292                             + (CellY * Bda->ScreenColumns + CellX) * sizeof(WORD),
293                             (LPVOID)&DriverState.Character,
294                             sizeof(WORD));
295     }
296     else if (Bda->VideoMode == 0x12)
297     {
298         INT i, j;
299         BYTE OldMask;
300 
301         /* Save the write mask */
302         IOWriteB(VGA_SEQ_INDEX, VGA_SEQ_MASK_REG);
303         OldMask = IOReadB(VGA_SEQ_DATA);
304 
305         for (i = 0; i < 16; i++)
306         {
307             WORD CursorLine[4] = {0};
308             DWORD VideoAddress = TO_LINEAR(GRAPHICS_VIDEO_SEG, Bda->VideoPage * Bda->VideoPageSize)
309                                  + ((DriverState.Position.Y + i) * 640 + DriverState.Position.X) / 8;
310 
311             /* Restore the data that was below the cursor */
312             for (j = 0; j < 16; j++)
313             {
314                 if (DriverState.GraphicsData[i * 16 + j] & (1 << 0)) CursorLine[0] |= 1 << j;
315                 if (DriverState.GraphicsData[i * 16 + j] & (1 << 1)) CursorLine[1] |= 1 << j;
316                 if (DriverState.GraphicsData[i * 16 + j] & (1 << 2)) CursorLine[2] |= 1 << j;
317                 if (DriverState.GraphicsData[i * 16 + j] & (1 << 3)) CursorLine[3] |= 1 << j;
318             }
319 
320             for (j = 0; j < 4; j++)
321             {
322                 /* Select the writing plane */
323                 IOWriteB(VGA_SEQ_INDEX, VGA_SEQ_MASK_REG);
324                 IOWriteB(VGA_SEQ_DATA, 1 << j);
325 
326                 /* Write the original data for this scanline */
327                 EmulatorWriteMemory(&EmulatorContext, VideoAddress, &CursorLine[j], sizeof(CursorLine[j]));
328             }
329         }
330 
331         /* Restore the old mask */
332         IOWriteB(VGA_SEQ_INDEX, VGA_SEQ_MASK_REG);
333         IOWriteB(VGA_SEQ_DATA, OldMask);
334     }
335     else if (Bda->VideoMode == 0x13)
336     {
337         INT i;
338 
339         for (i = 0; i < 16; i++)
340         {
341             DWORD VideoAddress = TO_LINEAR(GRAPHICS_VIDEO_SEG, Bda->VideoPage * Bda->VideoPageSize)
342                                  + (DriverState.Position.Y + i) * 320 + DriverState.Position.X;
343 
344             /* Write the original data for this scanline */
345             EmulatorWriteMemory(&EmulatorContext,
346                                 VideoAddress,
347                                 &DriverState.GraphicsData[i * 16],
348                                 16 * sizeof(BYTE));
349         }
350     }
351     else
352     {
353         // TODO: NOT IMPLEMENTED
354         UNIMPLEMENTED;
355     }
356 }
357 
358 static VOID ToMouseCoordinates(PCOORD Position)
359 {
360     COORD Resolution = VgaGetDisplayResolution();
361     DWORD Width = DriverState.MaxX - DriverState.MinX + 1;
362     DWORD Height = DriverState.MaxY - DriverState.MinY + 1;
363 
364     if (!VgaGetDoubleVisionState(NULL, NULL))
365     {
366         Resolution.X *= 8;
367         Resolution.Y *= 8;
368     }
369 
370     Position->X = DriverState.MinX + ((Position->X * Width) / Resolution.X);
371     Position->Y = DriverState.MinY + ((Position->Y * Height) / Resolution.Y);
372 }
373 
374 static VOID FromMouseCoordinates(PCOORD Position)
375 {
376     COORD Resolution = VgaGetDisplayResolution();
377     DWORD Width = DriverState.MaxX - DriverState.MinX + 1;
378     DWORD Height = DriverState.MaxY - DriverState.MinY + 1;
379 
380     if (!VgaGetDoubleVisionState(NULL, NULL))
381     {
382         Resolution.X *= 8;
383         Resolution.Y *= 8;
384     }
385 
386     Position->X = ((Position->X - DriverState.MinX) * Resolution.X) / Width;
387     Position->Y = ((Position->Y - DriverState.MinY) * Resolution.Y) / Height;
388 }
389 
390 static VOID CallMouseUserHandlers(USHORT CallMask)
391 {
392     USHORT i;
393     USHORT AX, BX, CX, DX, BP, SI, DI, DS, ES;
394     COORD Position = DriverState.Position;
395 
396     ToMouseCoordinates(&Position);
397 
398     /* Call handler 0 */
399     if ((DriverState.Handler0.CallMask & CallMask) != 0 &&
400          DriverState.Handler0.Callback != NULL32)
401     {
402         /*
403          * Set the parameters for the callback.
404          * NOTE: In text modes, the row and column will be reported
405          *       as a multiple of the cell size, typically 8x8 pixels.
406          */
407 
408         AX = getAX();
409         BX = getBX();
410         CX = getCX();
411         DX = getDX();
412         BP = getBP();
413         SI = getSI();
414         DI = getDI();
415         DS = getDS();
416         ES = getES();
417 
418         setAX(CallMask);
419         setBX(DriverState.ButtonState);
420         setCX(Position.X);
421         setDX(Position.Y);
422         setSI(MICKEYS_PER_CELL_HORIZ);
423         setDI(MICKEYS_PER_CELL_VERT);
424 
425         DPRINT("Calling Handler0 %04X:%04X with CallMask 0x%04X\n",
426                HIWORD(DriverState.Handler0.Callback),
427                LOWORD(DriverState.Handler0.Callback),
428                CallMask);
429 
430         /* Call the callback */
431         RunCallback16(&MouseContext, DriverState.Handler0.Callback);
432 
433         setAX(AX);
434         setBX(BX);
435         setCX(CX);
436         setDX(DX);
437         setBP(BP);
438         setSI(SI);
439         setDI(DI);
440         setDS(DS);
441         setES(ES);
442     }
443 
444     for (i = 0; i < ARRAYSIZE(DriverState.Handlers); ++i)
445     {
446         /* Call the suitable handlers */
447         if ((DriverState.Handlers[i].CallMask & CallMask) != 0 &&
448              DriverState.Handlers[i].Callback != NULL32)
449         {
450             /*
451              * Set the parameters for the callback.
452              * NOTE: In text modes, the row and column will be reported
453              *       as a multiple of the cell size, typically 8x8 pixels.
454              */
455 
456             AX = getAX();
457             BX = getBX();
458             CX = getCX();
459             DX = getDX();
460             BP = getBP();
461             SI = getSI();
462             DI = getDI();
463             DS = getDS();
464             ES = getES();
465 
466             setAX(CallMask);
467             setBX(DriverState.ButtonState);
468             setCX(Position.X);
469             setDX(Position.Y);
470             setSI(MICKEYS_PER_CELL_HORIZ);
471             setDI(MICKEYS_PER_CELL_VERT);
472 
473             DPRINT1("Calling Handler[%d] %04X:%04X with CallMask 0x%04X\n",
474                     i,
475                     HIWORD(DriverState.Handlers[i].Callback),
476                     LOWORD(DriverState.Handlers[i].Callback),
477                     CallMask);
478 
479             /* Call the callback */
480             RunCallback16(&MouseContext, DriverState.Handlers[i].Callback);
481 
482             setAX(AX);
483             setBX(BX);
484             setCX(CX);
485             setDX(DX);
486             setBP(BP);
487             setSI(SI);
488             setDI(DI);
489             setDS(DS);
490             setES(ES);
491         }
492     }
493 }
494 
495 static inline VOID DosUpdatePosition(PCOORD NewPosition)
496 {
497     COORD Resolution = VgaGetDisplayResolution();
498 
499     /* Check for text mode */
500     if (!VgaGetDoubleVisionState(NULL, NULL))
501     {
502         Resolution.X *= 8;
503         Resolution.Y *= 8;
504     }
505 
506     if (DriverState.ShowCount > 0) EraseMouseCursor();
507     DriverState.Position = *NewPosition;
508     if (DriverState.ShowCount > 0) PaintMouseCursor();
509 
510     /* Call the mouse handlers */
511     CallMouseUserHandlers(0x0001); // We use MS MOUSE v1.0+ format
512 }
513 
514 static inline VOID DosUpdateButtons(BYTE ButtonState) // WORD ButtonState
515 {
516     USHORT i;
517     USHORT CallMask = 0x0000; // We use MS MOUSE v1.0+ format
518 
519     for (i = 0; i < NUM_MOUSE_BUTTONS; i++)
520     {
521         BOOLEAN OldState = (DriverState.ButtonState >> i) & 1;
522         BOOLEAN NewState = (ButtonState >> i) & 1;
523 
524         if (NewState > OldState)
525         {
526             /* Mouse press */
527             DriverState.PressCount[i]++;
528             DriverState.LastPress[i] = DriverState.Position;
529 
530             CallMask |= (1 << (2 * i + 1));
531         }
532         else if (NewState < OldState)
533         {
534             /* Mouse release */
535             DriverState.ReleaseCount[i]++;
536             DriverState.LastRelease[i] = DriverState.Position;
537 
538             CallMask |= (1 << (2 * i + 2));
539         }
540     }
541 
542     DriverState.ButtonState = ButtonState;
543 
544     /* Call the mouse handlers */
545     CallMouseUserHandlers(CallMask);
546 }
547 
548 static VOID WINAPI DosMouseIrq(LPWORD Stack)
549 {
550     BYTE Flags;
551     SHORT DeltaX, DeltaY;
552     COORD Position;
553     BYTE ButtonState;
554 
555     /* Read the whole packet at once */
556     Flags = IOReadB(PS2_DATA_PORT);
557     PS2PortQueueRead(1); // NOTE: Should be a IOReadB! But see r67231
558     DeltaX = IOReadB(PS2_DATA_PORT);
559     PS2PortQueueRead(1); // NOTE: Should be a IOReadB! But see r67231
560     DeltaY = IOReadB(PS2_DATA_PORT);
561 
562     /* Adjust the sign */
563     if (Flags & MOUSE_X_SIGN) DeltaX = -DeltaX;
564     if (Flags & MOUSE_Y_SIGN) DeltaY = -DeltaY;
565 
566     /* Update the counters */
567     DriverState.HorizCount += DeltaX;
568     DriverState.VertCount  += DeltaY;
569 
570     /*
571      * Get the absolute position directly from the mouse, this is the only
572      * way to perfectly synchronize the host and guest mouse pointer.
573      */
574     MouseGetDataFast(&Position, &ButtonState);
575 
576     /* Call the update subroutines */
577     DosUpdatePosition(&Position);
578     DosUpdateButtons(ButtonState);
579 
580     /* Complete the IRQ */
581     PicIRQComplete(LOBYTE(Stack[STACK_INT_NUM]));
582 }
583 
584 static VOID WINAPI DosMouseService(LPWORD Stack)
585 {
586     switch (getAX())
587     {
588         /* Reset Driver */
589         case 0x00:
590         {
591             SHORT i;
592 
593             DriverState.ShowCount = 0;
594             DriverState.ButtonState = 0;
595 
596             /* Initialize the default clipping range */
597             DriverState.MinX = 0;
598             DriverState.MaxX = MOUSE_MAX_HORIZ - 1;
599             DriverState.MinY = 0;
600             DriverState.MaxY = MOUSE_MAX_VERT - 1;
601 
602             /* Set the default text cursor */
603             DriverState.TextCursor.ScreenMask = 0xFFFF; /* Display everything */
604             DriverState.TextCursor.CursorMask = 0xFF00; /* ... but with inverted attributes */
605 
606             /* Set the default graphics cursor */
607             DriverState.GraphicsCursor.HotSpot.X = 3;
608             DriverState.GraphicsCursor.HotSpot.Y = 1;
609 
610             RtlCopyMemory(DriverState.GraphicsCursor.ScreenMask,
611                           DefaultGfxScreenMask,
612                           sizeof(DriverState.GraphicsCursor.ScreenMask));
613 
614             RtlCopyMemory(DriverState.GraphicsCursor.CursorMask,
615                           DefaultGfxCursorMask,
616                           sizeof(DriverState.GraphicsCursor.CursorMask));
617 
618             /* Initialize the counters */
619             DriverState.HorizCount = DriverState.VertCount = 0;
620 
621             for (i = 0; i < NUM_MOUSE_BUTTONS; i++)
622             {
623                 DriverState.PressCount[i] = DriverState.ReleaseCount[i] = 0;
624             }
625 
626             /* Return mouse information */
627             setAX(0xFFFF);  // Hardware & driver installed
628             setBX(NUM_MOUSE_BUTTONS);
629 
630             break;
631         }
632 
633         /* Show Mouse Cursor */
634         case 0x01:
635         {
636             DriverState.ShowCount++;
637             if (DriverState.ShowCount == 1) PaintMouseCursor();
638 
639             break;
640         }
641 
642         /* Hide Mouse Cursor */
643         case 0x02:
644         {
645             DriverState.ShowCount--;
646             if (DriverState.ShowCount == 0) EraseMouseCursor();
647 
648             break;
649         }
650 
651         /* Return Position and Button Status */
652         case 0x03:
653         {
654             COORD Position = DriverState.Position;
655             ToMouseCoordinates(&Position);
656 
657             setBX(DriverState.ButtonState);
658             setCX(Position.X);
659             setDX(Position.Y);
660             break;
661         }
662 
663         /* Position Mouse Cursor */
664         case 0x04:
665         {
666             COORD Position = { getCX(), getDX() };
667             FromMouseCoordinates(&Position);
668 
669             DriverState.Position = Position;
670             break;
671         }
672 
673         /* Return Button Press Data */
674         case 0x05:
675         {
676             WORD Button = getBX();
677             COORD LastPress = DriverState.LastPress[Button];
678             ToMouseCoordinates(&LastPress);
679 
680             setAX(DriverState.ButtonState);
681             setBX(DriverState.PressCount[Button]);
682             setCX(LastPress.X);
683             setDX(LastPress.Y);
684 
685             /* Reset the counter */
686             DriverState.PressCount[Button] = 0;
687 
688             break;
689         }
690 
691         /* Return Button Release Data */
692         case 0x06:
693         {
694             WORD Button = getBX();
695             COORD LastRelease = DriverState.LastRelease[Button];
696             ToMouseCoordinates(&LastRelease);
697 
698             setAX(DriverState.ButtonState);
699             setBX(DriverState.ReleaseCount[Button]);
700             setCX(LastRelease.X);
701             setDX(LastRelease.Y);
702 
703             /* Reset the counter */
704             DriverState.ReleaseCount[Button] = 0;
705 
706             break;
707 
708         }
709 
710         /* Define Horizontal Cursor Range */
711         case 0x07:
712         {
713             WORD Min = getCX();
714             WORD Max = getDX();
715 
716             if (!VgaGetDoubleVisionState(NULL, NULL))
717             {
718                 /* Text mode */
719                 Min &= ~0x07;
720                 Max |= 0x07;
721             }
722 
723             DPRINT("Setting mouse horizontal range: %u - %u\n", Min, Max);
724             DriverState.MinX = Min;
725             DriverState.MaxX = Max;
726             break;
727         }
728 
729         /* Define Vertical Cursor Range */
730         case 0x08:
731         {
732             WORD Min = getCX();
733             WORD Max = getDX();
734 
735             if (!VgaGetDoubleVisionState(NULL, NULL))
736             {
737                 /* Text mode */
738                 Min &= ~0x07;
739                 Max |= 0x07;
740             }
741 
742             DPRINT("Setting mouse vertical range: %u - %u\n", Min, Max);
743             DriverState.MinY = Min;
744             DriverState.MaxY = Max;
745             break;
746         }
747 
748         /* Define Graphics Cursor */
749         case 0x09:
750         {
751             PWORD MaskBitmap = (PWORD)SEG_OFF_TO_PTR(getES(), getDX());
752 
753             DriverState.GraphicsCursor.HotSpot.X = getBX();
754             DriverState.GraphicsCursor.HotSpot.Y = getCX();
755 
756             RtlCopyMemory(DriverState.GraphicsCursor.ScreenMask,
757                           MaskBitmap,
758                           sizeof(DriverState.GraphicsCursor.ScreenMask));
759 
760             RtlCopyMemory(DriverState.GraphicsCursor.CursorMask,
761                           &MaskBitmap[16],
762                           sizeof(DriverState.GraphicsCursor.CursorMask));
763 
764             break;
765         }
766 
767         /* Define Text Cursor */
768         case 0x0A:
769         {
770             USHORT BX = getBX();
771 
772             if (BX == 0x0000)
773             {
774                 /* Define software cursor */
775                 DriverState.TextCursor.ScreenMask = getCX();
776                 DriverState.TextCursor.CursorMask = getDX();
777             }
778             else if (BX == 0x0001)
779             {
780                 /* Define hardware cursor */
781                 DPRINT1("Defining hardware cursor is unimplemented\n");
782                 UNIMPLEMENTED;
783                 // CX == start scan line
784                 // DX == end scan line
785             }
786             else
787             {
788                 DPRINT1("Invalid BX value 0x%04X\n", BX);
789             }
790 
791             break;
792         }
793 
794         /* Read Motion Counters */
795         case 0x0B:
796         {
797             setCX(DriverState.HorizCount);
798             setDX(DriverState.VertCount);
799 
800             /* Reset the counters */
801             DriverState.HorizCount = DriverState.VertCount = 0;
802 
803             break;
804         }
805 
806         /* Define Interrupt Subroutine Parameters, compatible MS MOUSE v1.0+ */
807         case 0x0C:
808         {
809             DriverState.Handler0.CallMask = getCX();
810             DriverState.Handler0.Callback = MAKELONG(getDX(), getES()); // Far pointer to the callback
811             DPRINT1("Define callback 0x%04X, %04X:%04X\n",
812                     DriverState.Handler0.CallMask,
813                     HIWORD(DriverState.Handler0.Callback),
814                     LOWORD(DriverState.Handler0.Callback));
815             break;
816         }
817 
818         /* Define Mickey/Pixel Ratio */
819         case 0x0F:
820         {
821             /* This call should be completely ignored */
822             break;
823         }
824 
825         /* Set Exclusion Area */
826         // http://www.ctyme.com/intr/rb-5972.htm
827         // http://www.techhelpmanual.com/849-int_33h_0010h__set_exclusion_area.html
828         //case 0x10:
829         //{
830         //}
831 
832         /* Define Double-Speed Threshold */
833         case 0x13:
834         {
835             DPRINT1("INT 33h, AH=13h: Mouse double-speed threshold is UNSUPPORTED\n");
836             break;
837         }
838 
839         /* Exchange Interrupt Subroutines, compatible MS MOUSE v3.0+ (see function 0x0C) */
840         case 0x14:
841         {
842             USHORT OldCallMask = DriverState.Handler0.CallMask;
843             ULONG  OldCallback = DriverState.Handler0.Callback;
844 
845             DriverState.Handler0.CallMask = getCX();
846             DriverState.Handler0.Callback = MAKELONG(getDX(), getES()); // Far pointer to the callback
847             DPRINT1("Exchange old callback 0x%04X, %04X:%04X with new callback 0x%04X, %04X:%04X\n",
848                     OldCallMask,
849                     HIWORD(OldCallback),
850                     LOWORD(OldCallback),
851                     DriverState.Handler0.CallMask,
852                     HIWORD(DriverState.Handler0.Callback),
853                     LOWORD(DriverState.Handler0.Callback));
854 
855             /* Return old callmask in CX and callback vector in ES:DX */
856             setCX(OldCallMask);
857             setES(HIWORD(OldCallback));
858             setDX(LOWORD(OldCallback));
859             break;
860         }
861 
862         /* Return Driver Storage Requirements */
863         case 0x15:
864         {
865             setBX(sizeof(MOUSE_DRIVER_STATE));
866             break;
867         }
868 
869         /* Save Driver State */
870         case 0x16:
871         {
872             /* Check whether the user buffer has correct size and fail if not */
873             if (getBX() != sizeof(MOUSE_DRIVER_STATE)) break;
874 
875             *((PMOUSE_DRIVER_STATE)SEG_OFF_TO_PTR(getES(), getDX())) = DriverState;
876             break;
877         }
878 
879         /* Restore Driver State */
880         case 0x17:
881         {
882             /* Check whether the user buffer has correct size and fail if not */
883             if (getBX() != sizeof(MOUSE_DRIVER_STATE)) break;
884 
885             DriverState = *((PMOUSE_DRIVER_STATE)SEG_OFF_TO_PTR(getES(), getDX()));
886             break;
887         }
888 
889         /* Set Alternate Mouse User Handler, compatible MS MOUSE v6.0+ */
890         case 0x18:
891         {
892             /*
893              * Up to three handlers can be defined by separate calls to this
894              * function, each with a different combination of shift states in
895              * the call mask; calling this function again with a call mask of
896              * 0000h undefines the specified handler (official documentation);
897              * specifying the same call mask and an address of 0000h:0000h
898              * undefines the handler (real life).
899              * See Ralf Brown: http://www.ctyme.com/intr/rb-5981.htm
900              * for more information.
901              */
902 
903             USHORT i;
904             USHORT CallMask = getCX();
905             ULONG  Callback = MAKELONG(getDX(), getES()); // Far pointer to the callback
906             BOOLEAN Success = FALSE;
907 
908             DPRINT1("Define v6.0+ callback 0x%04X, %04X:%04X\n",
909                     CallMask, HIWORD(Callback), LOWORD(Callback));
910 
911             if (CallMask == 0x0000)
912             {
913                 /*
914                  * Find the handler entry corresponding to the given
915                  * callback and undefine it.
916                  */
917                 for (i = 0; i < ARRAYSIZE(DriverState.Handlers); ++i)
918                 {
919                     if (DriverState.Handlers[i].Callback == Callback)
920                     {
921                         /* Found it, undefine the handler */
922                         DriverState.Handlers[i].CallMask = 0x0000;
923                         DriverState.Handlers[i].Callback = NULL32;
924                         Success = TRUE;
925                         break;
926                     }
927                 }
928             }
929             else if (Callback == NULL32)
930             {
931                 /*
932                  * Find the handler entry corresponding to the given
933                  * callmask and undefine it.
934                  */
935                 for (i = 0; i < ARRAYSIZE(DriverState.Handlers); ++i)
936                 {
937                     if (DriverState.Handlers[i].CallMask == CallMask)
938                     {
939                         /* Found it, undefine the handler */
940                         DriverState.Handlers[i].CallMask = 0x0000;
941                         DriverState.Handlers[i].Callback = NULL32;
942                         Success = TRUE;
943                         break;
944                     }
945                 }
946             }
947             else
948             {
949                 /*
950                  * Try to find a handler entry corresponding to the given
951                  * callmask to redefine it, otherwise find an empty handler
952                  * entry and set the new handler in there.
953                  */
954 
955                 USHORT EmptyHandler = 0xFFFF; // Invalid handler
956 
957                 for (i = 0; i < ARRAYSIZE(DriverState.Handlers); ++i)
958                 {
959                     /* Find the first empty handler */
960                     if (EmptyHandler == 0xFFFF &&
961                         DriverState.Handlers[i].CallMask == 0x0000 &&
962                         DriverState.Handlers[i].Callback == NULL32)
963                     {
964                         EmptyHandler = i;
965                     }
966 
967                     if (DriverState.Handlers[i].CallMask == CallMask)
968                     {
969                         /* Found it, redefine the handler */
970                         DriverState.Handlers[i].CallMask = CallMask;
971                         DriverState.Handlers[i].Callback = Callback;
972                         Success = TRUE;
973                         break;
974                     }
975                 }
976 
977                 /*
978                  * If we haven't found anything and we found
979                  * an empty handler, set it.
980                  */
981                 if (!Success && EmptyHandler != 0xFFFF
982                     /* && EmptyHandler < ARRAYSIZE(DriverState.Handlers) */)
983                 {
984                     DriverState.Handlers[EmptyHandler].CallMask = CallMask;
985                     DriverState.Handlers[EmptyHandler].Callback = Callback;
986                     Success = TRUE;
987                 }
988             }
989 
990             /* If we failed, set error code */
991             if (!Success) setAX(0xFFFF);
992 
993             break;
994         }
995 
996         /* Return User Alternate Interrupt Vector, compatible MS MOUSE v6.0+ */
997         case 0x19:
998         {
999             USHORT i;
1000             USHORT CallMask = getCX();
1001             ULONG  Callback;
1002             BOOLEAN Success = FALSE;
1003 
1004             /*
1005              * Find the handler entry corresponding to the given callmask.
1006              */
1007             for (i = 0; i < ARRAYSIZE(DriverState.Handlers); ++i)
1008             {
1009                 if (DriverState.Handlers[i].CallMask == CallMask)
1010                 {
1011                     /* Found it */
1012                     Callback = DriverState.Handlers[i].Callback;
1013                     Success = TRUE;
1014                     break;
1015                 }
1016             }
1017 
1018             if (Success)
1019             {
1020                 /* Return the callback vector in BX:DX */
1021                 setBX(HIWORD(Callback));
1022                 setDX(LOWORD(Callback));
1023             }
1024             else
1025             {
1026                 /* We failed, set error code */
1027                 setCX(0x0000);
1028             }
1029 
1030             break;
1031         }
1032 
1033         /* Set Mouse Sensitivity */
1034         case 0x1A:
1035         {
1036             DPRINT1("INT 33h, AH=1Ah: Mouse sensitivity is UNSUPPORTED\n");
1037 
1038             // FIXME: Do that at runtime!
1039 
1040             // UCHAR BH = getBH();
1041             // setBH(0x00);
1042             // BiosPs2Service(0x00);
1043             // FIXME: Check for return status in AH and CF
1044             // setBH(BH);
1045 
1046             break;
1047         }
1048 
1049         /* Return Mouse Sensitivity */
1050         case 0x1B:
1051         {
1052             DPRINT1("INT 33h, AH=1Bh: Mouse sensitivity is UNSUPPORTED\n");
1053 
1054             /* Return default values */
1055             setBX(50); // Horizontal speed
1056             setCX(50); //   Vertical speed
1057             setDX(50); // Double speed threshold
1058 
1059             // FIXME: Get that at runtime!
1060 
1061             // UCHAR BH = getBH();
1062             // setBH(0x00);
1063             // BiosPs2Service(0x00);
1064             // FIXME: Check for return status in AH and CF
1065             // setBH(BH);
1066 
1067             break;
1068         }
1069 
1070         /* Disable Mouse Driver */
1071         case 0x1F:
1072         {
1073             /* INT 33h vector before the mouse driver was first installed */
1074             setES(HIWORD(OldIntHandler));
1075             setBX(LOWORD(OldIntHandler));
1076 
1077             DosMouseDisable();
1078             // UCHAR BH = getBH();
1079             // setBH(0x00);
1080             // BiosPs2Service(0x00);
1081             // FIXME: Check for return status in AH and CF
1082             // setBH(BH);
1083             break;
1084         }
1085 
1086         /* Enable Mouse Driver */
1087         case 0x20:
1088         {
1089             DosMouseEnable();
1090             // UCHAR BH = getBH();
1091             // setBH(0x01);
1092             // BiosPs2Service(0x00);
1093             // FIXME: Check for return status in AH and CF
1094             // setBH(BH);
1095             break;
1096         }
1097 
1098         /* Software Reset */
1099         case 0x21:
1100         {
1101             /*
1102              * See: http://www.htl-steyr.ac.at/~morg/pcinfo/hardware/interrupts/inte3sq8.htm
1103              * for detailed information and differences with respect to subfunction 0x00:
1104              * http://www.htl-steyr.ac.at/~morg/pcinfo/hardware/interrupts/inte3j74.htm
1105              */
1106 
1107             SHORT i;
1108 
1109             DriverState.ShowCount = 0;
1110             DriverState.ButtonState = 0;
1111 
1112             /* Initialize the default clipping range */
1113             DriverState.MinX = 0;
1114             DriverState.MaxX = MOUSE_MAX_HORIZ - 1;
1115             DriverState.MinY = 0;
1116             DriverState.MaxY = MOUSE_MAX_VERT - 1;
1117 
1118             /* Initialize the counters */
1119             DriverState.HorizCount = DriverState.VertCount = 0;
1120 
1121             for (i = 0; i < NUM_MOUSE_BUTTONS; i++)
1122             {
1123                 DriverState.PressCount[i] = DriverState.ReleaseCount[i] = 0;
1124             }
1125 
1126             /* Return mouse information */
1127             setAX(0xFFFF);  // Hardware & driver installed
1128             setBX(NUM_MOUSE_BUTTONS);
1129 
1130             break;
1131         }
1132 
1133         /* Get Software Version, Mouse Type, and IRQ Number, compatible MS MOUSE v6.26+ */
1134         case 0x24:
1135         {
1136             setBX(MOUSE_VERSION); // Version Number
1137 
1138             /*
1139              * See Ralf Brown: http://www.ctyme.com/intr/rb-5993.htm
1140              * for the list of possible values.
1141              */
1142             // FIXME: To be determined at runtime!
1143             setCH(0x04); // PS/2 Type
1144             setCL(0x00); // PS/2 Interrupt
1145 
1146             break;
1147         }
1148 
1149         // BIOS Function INT 33h, AX = 0x0025 NOT IMPLEMENTED
1150         case 0x25:
1151         {
1152             setAX(0);
1153             setBX(0);
1154             setCX(0);
1155             setDX(0);
1156             UNIMPLEMENTED;
1157             break;
1158         }
1159 
1160         /* Get Maximum Virtual Coordinates */
1161         case 0x26:
1162         {
1163             setBX(!DriverEnabled);
1164             // FIXME: In fact the MaxX and MaxY here are
1165             // theoretical values for the current video mode.
1166             // They therefore can be different from the current
1167             // min/max values.
1168             // See http://www.ctyme.com/intr/rb-5995.htm
1169             // for more details.
1170             setCX(DriverState.MaxX);
1171             setDX(DriverState.MaxY);
1172             break;
1173         }
1174 
1175         /* Get Current Minimum/Maximum Virtual Coordinates */
1176         case 0x31:
1177         {
1178             setAX(DriverState.MinX);
1179             setBX(DriverState.MinY);
1180             setCX(DriverState.MaxX);
1181             setDX(DriverState.MaxY);
1182             break;
1183         }
1184 
1185 #if 0
1186         case 0x33:
1187         {
1188               /*
1189               * Related to http://www.ctyme.com/intr/rb-5985.htm
1190               * INT 33h, AX=001Ch "SET INTERRUPT RATE":
1191 
1192               * Values for mouse interrupt rate:
1193               * BX = rate
1194                 00h    no interrupts allowed
1195                 01h    30 per second
1196                 02h    50 per second
1197                 03h    100 per second
1198                 04h    200 per second
1199               */
1200         }
1201 #endif
1202 
1203         /* Return Pointer to Copyright String */
1204         case 0x4D:
1205         {
1206             setES(MouseDataSegment);
1207             setDI(FIELD_OFFSET(MOUSE_DRIVER, Copyright));
1208             break;
1209         }
1210 
1211         /* Get Version String (pointer) */
1212         case 0x6D:
1213         {
1214             /*
1215              * The format of the version "string" is:
1216              * Offset  Size    Description
1217              * 00h     BYTE    major version
1218              * 01h     BYTE    minor version (BCD)
1219              */
1220             setES(MouseDataSegment);
1221             setDI(FIELD_OFFSET(MOUSE_DRIVER, Version));
1222             break;
1223         }
1224 
1225         default:
1226         {
1227             DPRINT1("BIOS Function INT 33h, AX = 0x%04X NOT IMPLEMENTED\n", getAX());
1228         }
1229     }
1230 }
1231 
1232 /* PUBLIC FUNCTIONS ***********************************************************/
1233 
1234 static
1235 VOID DosMouseEnable(VOID)
1236 {
1237     if (DriverEnabled) return;
1238 
1239     DriverEnabled = TRUE;
1240 
1241     /* Get the old IRQ handler */
1242     OldIrqHandler = ((PDWORD)BaseAddress)[MOUSE_IRQ_INT];
1243 
1244     /* Set the IRQ handler */
1245     RegisterInt32(MAKELONG(FIELD_OFFSET(MOUSE_DRIVER, MouseIrqInt16Stub), MouseDataSegment),
1246                   MOUSE_IRQ_INT, DosMouseIrq, NULL);
1247 }
1248 
1249 static
1250 VOID DosMouseDisable(VOID)
1251 {
1252     if (!DriverEnabled) return;
1253 
1254     /* Restore the old IRQ handler */
1255     ((PDWORD)BaseAddress)[MOUSE_IRQ_INT] = OldIrqHandler;
1256 
1257     DriverEnabled = FALSE;
1258 }
1259 
1260 BOOLEAN DosMouseInitialize(VOID)
1261 {
1262     /* Initialize some memory for storing our data that should be available to DOS */
1263     MouseDataSegment = DosAllocateMemory(sizeof(MOUSE_DRIVER), NULL);
1264     if (MouseDataSegment == 0) return FALSE;
1265     MouseData = (PMOUSE_DRIVER)SEG_OFF_TO_PTR(MouseDataSegment, 0x0000);
1266 
1267     /* Initialize the callback context */
1268     InitializeContext(&MouseContext, MouseDataSegment, FIELD_OFFSET(MOUSE_DRIVER, MouseContextScratch));
1269 
1270     /* Clear the state */
1271     RtlZeroMemory(&DriverState, sizeof(DriverState));
1272 
1273     /* Mouse Driver Copyright */
1274     RtlCopyMemory(MouseData->Copyright, MouseCopyright, sizeof(MouseCopyright)-1);
1275 
1276     /* Mouse Driver Version in BCD format, compatible MS-MOUSE */
1277     MouseData->Version = MAKEWORD(MOUSE_VERSION/0x0100, MOUSE_VERSION%0x0100);
1278 
1279     /* Get the old mouse service interrupt handler */
1280     OldIntHandler = ((PDWORD)BaseAddress)[DOS_MOUSE_INTERRUPT];
1281 
1282     /* Initialize the interrupt handler */
1283     RegisterInt32(MAKELONG(FIELD_OFFSET(MOUSE_DRIVER, MouseDosInt16Stub), MouseDataSegment),
1284                   DOS_MOUSE_INTERRUPT, DosMouseService, NULL);
1285 
1286     DosMouseEnable();
1287     // UCHAR BH = getBH();
1288     // setBH(0x01);
1289     // BiosPs2Service(0x00);
1290     // FIXME: Check for return status in AH and CF
1291     // setBH(BH);
1292 
1293     return TRUE;
1294 }
1295 
1296 VOID DosMouseCleanup(VOID)
1297 {
1298     if (DriverState.ShowCount > 0) EraseMouseCursor();
1299     DosMouseDisable();
1300     // UCHAR BH = getBH();
1301     // setBH(0x00);
1302     // BiosPs2Service(0x00);
1303     // FIXME: Check for return status in AH and CF
1304     // setBH(BH);
1305 
1306     /* Restore the old mouse service interrupt handler */
1307     ((PDWORD)BaseAddress)[DOS_MOUSE_INTERRUPT] = OldIntHandler;
1308 
1309     DosFreeMemory(MouseDataSegment);
1310     MouseDataSegment = 0;
1311     MouseData = 0;
1312 }
1313 
1314 /* EOF */
1315