How do you do a real mode callback for the mouse?

DOS specific questions.
Post Reply
lassar
Posts: 306
Joined: Jan 17, 2006 1:35

How do you do a real mode callback for the mouse?

Post by lassar »

I have been trying to do a real mode callback for the mouse.

The best I can do is not crashing my program. Still no mouse.

Here is the code , that I have got, so far.

What am I doing wrong?

Code: Select all


SUB Initilize.Mouse
#IF 0
	regs.x.ax = 0
	__dpmi_int(&H33, @regs)
    MouseExist% = regs.x.ax

	' set horizontal range 
	regs.x.ax = 7
	regs.x.cx = 0
	regs.x.dx = 639
	__dpmi_int(&H33, @regs)

	' set vertical range 
	regs.x.ax = 8
	regs.x.cx = 0
	regs.x.dx = 479
	__dpmi_int(&H33, @regs)

	' ensure that the mouse isn't drawn by the mouse driver 
	regs.x.ax = 2
    
__dpmi_int(&H33, @regs)		

PositionMouse 320,240
#ENDIF

MouseHorz% = 320
MouseVert% = 240
OldMouseHorz% = 320
OldMouseVert% = 240
OldMouseButtons% = 0

__dpmi_allocate_real_mode_callback @GetTheMouse(), @regs, @MouseCallBack
	
' set user interrupt routine 
	
regs.x.ax = &H0C
regs.x.cx = &H7F
regs.x.es = MouseCallBack.segment
regs.x.dx = MouseCallBack.offset16
__dpmi_int(&H33, @regs)

END SUB

SUB GetTheMouse()
 #IF 1
 Buttons% =  regs.x.bx
 MouseHorz% = regs.x.cx
 MouseVert% = regs.x.dx
 
 IF GetCursorArea% = 1 AND (OldMouseHorz% <> MouseHorz% OR OldMouseVert% <> MouseVert%) THEN 
    PutMouseArea OldMouseHorz%,OldMouseVert%,CursorAreaPtr
 END IF

 GetCursorArea% = 1
 GetMouseArea MouseHorz%,MouseVert%,CursorAreaPtr
 PutMouseArea MouseHorz%,MouseVert%,CursorPtr     
 
 'RealDelay .1
 OldMouseHorz% = MouseHorz%
 OldMouseVert% = MouseVert%
#ENDIF
ASM IRET
END SUB
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Re: How do you do a real mode callback for the mouse?

Post by MichaelW »

This is the most recent version of my efforts, broken as noted.

Code: Select all

''=============================================================================
'' HANGS UNDER DOS DPMI, AND I HAVE NOT BEEN ABLE TO FIND THE PROBLEM.
''=============================================================================

''-----------------------------------------------------------------
'' This is a simplified version of the RM register data structure.
''-----------------------------------------------------------------

type DPMIREGS
    edi   as uinteger
    esi   as uinteger
    ebp   as uinteger
    res   as uinteger   '' Reserved
    ebx   as uinteger
    edx   as uinteger
    ecx   as uinteger
    eax   as uinteger
    flags as ushort
    es    as ushort
    ds    as ushort
    fs    as ushort
    gs    as ushort
    ip    as ushort
    cs    as ushort
    sp    as ushort
    ss    as ushort
end type

''---------------------------------------------------------------------------
'' And these macros make it easier to access the members from assembly code.
'' They expand to a memory operand that consists of a displacement from the
'' variable regs, so for example:
''    mov ax, es:[reax]
'' Expands to:
''    mov ax, es:[_REGS+28]
'' And since the memory operand does not specify a size, we can specify a
'' BYTE, WORD, or DWORD register as the destination operand, and AS will
'' encode the instruction appropriately. But note that when the memory
'' operand is the destination, to avoid potential problems the size of the
'' source operand should match the defined size of the destination operand.
''---------------------------------------------------------------------------

#define REDI    regs+offsetof(DPMIREGS,edi)
#define RESI    regs+offsetof(DPMIREGS,esi)
#define REBP    regs+offsetof(DPMIREGS,ebp)
#define REBX    regs+offsetof(DPMIREGS,ebx)
#define REDX    regs+offsetof(DPMIREGS,edx)
#define RECX    regs+offsetof(DPMIREGS,ecx)
#define REAX    regs+offsetof(DPMIREGS,eax)
#define RFLAGS  regs+offsetof(DPMIREGS,flags)
#define RES     regs+offsetof(DPMIREGS,es)
#define RDS     regs+offsetof(DPMIREGS,ds)
#define RFS     regs+offsetof(DPMIREGS,fs)
#define RGS     regs+offsetof(DPMIREGS,gs)
#define RIP     regs+offsetof(DPMIREGS,ip)
#define RCS     regs+offsetof(DPMIREGS,cs)
#define RSP     regs+offsetof(DPMIREGS,sp)
#define RSS     regs+offsetof(DPMIREGS,ss)

dim shared as DPMIREGS regs

''---------------------------------------
'' These are our mouse status variables:
''---------------------------------------

dim shared as integer mouseX      '' mouse X coordinate
dim shared as integer mouseY      '' mouse Y coordinate
dim shared as integer mouseLeft   '' mouse left-button state
dim shared as integer mouseRight  '' mouse right-button state
dim shared as integer mouseEvent  '' mouse event mask

''-----------------------------------------------------------------------
'' These are used to interpret the event mask. The assigned values are
'' the value of the corresponding bit in the event mask, so an event can
'' be detected by ANDing the appropriate constant with the event mask.
'' See the comments for the call to the mouse driver Set Interrupt
'' Subroutine Call MaskAnd Address function for more information.
''-----------------------------------------------------------------------

#define POSITION  1     '' cursor position changed
#define LPRESS    2     '' left button pressed
#define LRELEASE  4     '' left button released
#define RPRESS    8     '' right button pressed
#define RRELEASE  16    '' right button released

dim shared as ushort callSegment, callOffset

''=============================================================================
'' This is our PM callback procedure, effectively a PM interrupt subroutine
'' called by the mouse driver in response to a mouse hardware interrupt.
''
'' At entry:
''
''  Interrupts disabled
''  CS:(E)IP = selector:offset specified in call to function 0303H
''  DS:(E)SI = selector:offset corresponding to RM SS:SP
''  ES:(E)DI = selector:offset of RM register data structure
''  SS:(E)SP = locked protected mode stack provided by DPMI host
''  All other registers undefined
''
''=============================================================================

'' IN THE CALLBACK, IS THE RM REGISTER DATA STRUCTURE AT DS:ESI THE
'' ORIGINAL STRUCTURE, OR A MODIFIED COPY?

dim shared as any ptr addr

asm COUNT: .int 0

sub CallBack naked()
    asm
        ''-----------------------------------------------------------------
        '' We need to copy the mouse event data, sent by the mouse driver,
        '' to our global MOUSETYPE structure variable in the program data
        '' segment. Since this procedure is called with DS set to the RM
        '' stack instead of to the program data segment, to access the
        '' program data segment we need access to the program data segment
        '' selector. This is a common problem in interrupt handlers, where
        '' the only segment register with a known value is CS. The normal
        '' solution is to store the required data in the code segment.
        '' Here, we store only the data segment selector in the code
        '' segment, and use it to access the program data segment. And
        '' since the selector needs to be somewhere outside the execution
        '' path, a convenient place to store it is after the IRET below.
        ''
        '' Before accessing the data segment we must load the data segment
        '' selector into a segment register. Note that the default segment
        '' register for most instructions that access data is DS, and to
        '' use any other segment register you must prefix the memory
        '' operand with a segment override.
        ''-----------------------------------------------------------------

        ''-----------------------------------------------
        '' The event data is mapped to the registers as:
        ''    AX = condition mask
        ''    BX = button state
        ''    CX = horizontal cursor coordinate
        ''    DX = vertical cursor coordinate
        ''    SI = horizontal mouse counts (mickeys)
        ''    DI = vertical mouse counts (mickeys)
        '' Bit0 of the button status will be set if the
        '' left mouse button is pressed and bit1 if the
        '' right mouse button is pressed.
        ''-----------------------------------------------

        push es
        mov ax, cs:DS_SEL
        mov es, ax

        mov ax, es:[reax]
        movzx eax, ax
probe:
        mov es:[mouseEvent], eax     '' FAULT HERE

        inc DWORD PTR cs:[COUNT]

        mov ebx, es:[rebx]
        mov DWORD PTR es:[mouseLeft], 0
        test ebx, 1
        jz  0f
        mov DWORD PTR es:[mouseLeft], -1
      0:
        mov DWORD PTR es:[mouseRight], 0
        test ebx, 2
        jz  1f
        mov DWORD PTR es:[mouseRight], -1
      1:

        mov cx, es:[recx]
        movzx ecx, cx
        mov es:[mouseX], ecx

        mov dx, es:[redx]
        movzx edx, dx
        mov es:[mouseY], edx

        ''-----------------------------------------------------------
        '' Before we return to the caller we need to copy the return
        '' address from the RM stack to the CS and IP members of the
        '' register data structure. The return address was the last
        '' thing pushed onto the RM stack, the return CS first and
        '' the return IP last, so we get the IP member from DS:ESI
        '' and the CS member from DS:ESI+2.
        ''-----------------------------------------------------------

        '' ADDING DS OVERRIDE DID NOT CORRECT PROBLEM
        '' NEED TO RETHINK THIS

        mov ax, ds:[esi]
        mov es:[rip], ax
        mov ax, ds:[esi+2]
        mov es:[rcs], ax

        pop es
        iret

        ''--------------------------------
        '' This is our code-segment data.
        ''--------------------------------

        .balign 4
        DS_SEL: .short 0

    end asm
end sub

''=============================================================================


'sub _constructor() constructor
sub Init()
    asm

        ''-------------------------------------------------
        '' Get the mouse driver interrupt vector and check
        '' for segment address = 0 (= no mouse driver).
        ''-------------------------------------------------

        mov ax, 0x200
        mov bx, 0x33
        int 0x31
        jc  error
        test cx, cx
        jz  error

        ''-----------------------------------------------------
        '' Attempt to reset the mouse, fail = no mouse driver.
        ''-----------------------------------------------------

        xor ax, ax
        int 0x33
        cmp ax, -1
        jne error

        ''---------------------------------
        '' Allocate a RM callback address.
        ''---------------------------------

        push ds
        push es
        mov ax, 0x303
        push ds
        pop es
        push cs
        pop ds
        mov esi, OFFSET CallBack
        mov edi, OFFSET regs
        int 0x31
        pop es
        pop ds
        jc  error
        mov [callSegment], cx
        mov [callOffset], dx

        ''-------------------------------------------------------------------
        '' As noted above, the callback procedure needs to copy the mouse
        '' event data to our global MOUSETYPE structure variable. To do
        '' so it needs access to the program data segment selector, stored
        '' in the code segment.
        ''
        '' Since code segments are limited to execute-only or execute/read,
        '' to write to the code segment we need to use an alias descriptor,
        '' created with the DPMI Create Alias Descriptor function. The alias
        '' descriptor is identical to the original CS descriptor, except for
        '' the 4-bit Type field of the access byte. Where for the original
        '' CS descriptor the Type value 1011b specifies an execute/read code
        '' segment, for the alias descriptor the Type value 0010b specifies
        '' a read/write data segment. The function returns a selector in AX
        '' that we temporarily load into the ES segment register to store
        '' the data-segment selector to our code-segment data.
        ''-------------------------------------------------------------------

        push es
        mov ax, 0xa
        mov bx, cs
        int 0x31
        jnc 0f
        pop es
        jmp error
      0:
        mov es, ax
        mov ax, ds
        mov es:DS_SEL, ax
        pop es

        ''-----------------------------------------------------------
        '' Call the mouse driver Set Interrupt Subroutine Call Mask
        '' And Address function, setting the call mask to enable an
        '' interrupt for any of the possible conditions, and the sub
        '' address to the allocated callback address.
        ''
        '' The conditions are mapped to the call mask bits as:
        ''    0   cursor position changed
        ''    1   left button pressed
        ''    2   left button released
        ''    3   right button pressed
        ''    4   right button released
        ''
        '' This function requires that the caller load ES with the
        '' RM segment address of the interrupt sub. Since loading a
        '' RM segment address into a segment register from PM would
        '' trigger an exception, this function must be called via
        '' the DPMI Simulate Real Mode Interrupt function.
        ''-----------------------------------------------------------

        ''--------------------------------------------------------------
        '' Load the required members of the RM register data structure.
        ''--------------------------------------------------------------

        mov eax, 12
        mov [reax], eax
        mov ecx, &b11111
        mov [recx], ecx
        mov ax, [callSegment]
        mov [res], ax
        mov dx, [callOffset]
        movzx edx, dx
        mov [redx], edx

        ''----------------------------
        '' Simulate the RM interrupt.
        ''----------------------------

        mov ax, 0x300
        mov bx, 0x33
        xor cx, cx
        mov edi, OFFSET regs
        int 0x31

      error:
    end asm

    dim as short dssel
    dim as any ptr addr
    asm mov DWORD PTR [addr], OFFSET probe
    asm mov ax, cs:DS_SEL
    asm mov [dssel], ax
    'print dssel
    print hex(@CallBack)
    print hex(addr)
end sub


''=============================================================================

'sub _destructor() destructor
sub term()

    asm

        ''----------------------------------------------
        '' Reset the mouse to deactivate the interrupt.
        ''----------------------------------------------

        xor ax, ax
        int 0x33

        ''-------------------------------
        '' Free the RM callback address.
        ''-------------------------------

        mov ax, 0x304
        mov cx, [callSegment]
        mov dx, [callOffset]
        int 0x31

    end asm
end sub

''=============================================================================
dim as integer x
asm mov eax, cs:[COUNT]
asm mov [x], eax
Init()
for i as integer = 1 to 1000
    print x, mouseX, mouseY '', mouseLeft, mouseRight, mouseEvent
    sleep 30
next
Term()
sleep

/'
do
    print mouse.x,mouse.y,mouse.left,mouse.right,mouse.event
loop until inkey <> ""
print "done"
sleep
'/
I stopped working on this when I determined that the Microsoft mouse driver supports a more or less limited number of video modes, and the table in my Microsoft Mouse Programmer’s Reference from 1991 shows CGS/EGA/VGA/MCGA modes that you would expect, and a bunch of modes specific to obsolete display adapters, and no VESA anything. And specifying a supported mode number (in function 40) that matches the resolution and color depth of the actual mode is not sufficient to allow the mouse driver to function. In my tests of three versions, version 8.2a supported the greatest number of modes, ~60, and version 9.01 supported ~~30 modes, so it seems that a more recent version is unlikely to correct the problems. My test code:

Code: Select all

''=============================================================================

''-----------------------------------------------------------------------------
'' Function 41 was available starting with Microsoft Mouse Driver version 7.0.
''
'' This code ignores the description string that the function *may* return,
'' because displaying the string in PM would be difficult, and in my tests
'' of driver versions 8.2a and 9.01 no strings were returned. And after this
'' this test I decided to use version 8.2a because it supported ~2x as many
'' video modes.
''
'' Note that the video mode numbers may be out of numerical order and may be
'' repeated in the enumeration.
''-----------------------------------------------------------------------------

function EnumVideoModes naked(byval findFlag as integer) as integer
    asm
        push ebx
        mov ax, 41        '' function number
        mov ecx, [esp+8]  '' find flag, 0=find first, nonzero=find next
        int 0x33
        movzx eax, cx     '' return video mode number
        pop ebx
        ret               '' note that FBDOS uses the CDECL calling convention
    end asm
end function

''=============================================================================

dim as integer video_mode
dim as ushort mouse_driver_version
dim as string hval

asm
    mov ax, 36
    int 0x33
    mov [mouse_driver_version], bx
end asm

print hex(hibyte(mouse_driver_version));".";hex(lobyte(mouse_driver_version))

video_mode = EnumVideoModes(0)
do while video_mode > 0
    if len(hex(video_mode)) < 2 then print "0";
    print hex(video_mode);chr(9);
    video_mode = EnumVideoModes(1)
loop
print
sleep
Perhaps there is an alternative mouse driver available that can handle the VESA modes, at least as far as reporting the cursor position.
monochromator
Posts: 42
Joined: Mar 05, 2013 5:37

Re: How do you do a real mode callback for the mouse?

Post by monochromator »

To lassar:
You have absolutely wrong idea of use of real mode callbacks.
Actually everything is a little more difficult.
There is the example of the workable program below.

Code: Select all

'Использование звонковых точек реального режима для получения событий от мыши.

'$LANG: "fblite"
'$INCLUDE: 'dos\go32.bi'
'$INCLUDE: 'dos\dpmi.bi'
'$INCLUDE: 'dos\conio.bi'

TYPE RealModePtr FIELD = 1
 ROffs AS USHORT
 RSegm AS USHORT
END TYPE

TYPE VbeInfoBlock FIELD = 1
 VbeSignature AS UINTEGER 'VBE Signature
 VbeVersion AS USHORT 'VBE Version
 OemStringPtr AS RealModePtr 'Pointer to OEM String
 Capabilities AS UINTEGER 'Capabilities of graphics cont.
 VideoModePtr AS RealModePtr 'Pointer to Video Mode List
 TotalMemory AS USHORT 'Number of 64kb memory blocks
 OemSoftwareRev AS USHORT 'VBE implementation Software revision
 OemVendorNamePtr AS RealModePtr 'Pointer to Vendor Name String
 OemProductNamePtr AS RealModePtr 'Pointer to Product Name String
 OemProductRevPtr AS RealModePtr 'Pointer to Product Revision String
 VSReserved (0 TO 221) AS UBYTE 'Reserved for VBE implementation scratch area
 OemData (0 TO 255) AS UBYTE 'Data Area for OEM Strings
END TYPE

TYPE ModeInfoBlock FIELD = 1
 'Mandatory information for all VBE revisions
 ModeAttributes AS USHORT 'mode attributes
 WinAAttributes AS UBYTE 'window A attributes
 WinBAttributes AS UBYTE 'window B attributes
 WinGranularity AS USHORT 'window granularity
 WinSize AS USHORT 'window size
 WinASegment AS USHORT 'window A start segment
 WinBSegment AS USHORT 'window B start segment
 WinFuncPtr AS RealModePtr 'pointer to window function
 BytesPerScanLine AS USHORT 'bytes per scan line
 'Mandatory information for VBE 1.2 and above
 XResolution AS USHORT 'horizontal resolution in pixels or chars
 YResolution AS USHORT 'vertical resolution in pixels or chars
 XCharSize AS UBYTE 'character cell width in pixels
 YCharSize AS UBYTE 'character cell height in pixels
 NumberOfPlanes AS UBYTE 'number of memory planes
 BitsPerPixel AS UBYTE 'bits per pixel
 NumberOfBanks AS UBYTE 'number of banks
 MemoryModel AS UBYTE 'memory model type
 BankSize AS UBYTE 'bank size in KB
 NumberOfImagePages AS UBYTE 'number of images
 MBReserved AS UBYTE 'reserved for page function
 'Direct Color fields (required for direct/6 and YUV/7 memory models)
 RedMaskSize AS UBYTE 'size of direct color red mask in bits
 RedFieldPosition AS UBYTE 'bit position of lsb of red mask
 GreenMaskSize AS UBYTE 'size of direct color green mask in bits
 GreenFieldPosition AS UBYTE 'bit position of lsb of green mask
 BlueMaskSize AS UBYTE 'size of direct color blue mask in bits
 BlueFieldPosition AS UBYTE 'bit position of lsb of blue mask
 RsvdMaskSize AS UBYTE 'size of direct color reserved mask in bits
 RsvdFieldPosition AS UBYTE 'bit position of lsb of reserved mask
 DirectColorModeInfo AS UBYTE 'direct color mode attributes
 'Mandatory information for VBE 2.0 and above
 PhysBasePtr AS UINTEGER 'physical address for flat frame buffer
 OffScreenMemOffset AS UINTEGER 'pointer to start of off screen memory
 OffScreenMemSize AS USHORT 'amount of off screen memory in 1k units
 MB2Reserved (0 TO 205) AS UBYTE 'remainder of ModeInfoBlock
END TYPE

TYPE PaletteType FIELD = 1
 BlueByte AS UBYTE
 GreenByte AS UBYTE
 RedByte AS UBYTE
 ReservedByte AS UBYTE
END TYPE

TYPE MouseHandlerDataType
 MouseCursorMoved AS INTEGER
 ButtonPressed AS INTEGER
 ButtonStatus AS USHORT
 MouseCursorX AS USHORT
 MouseCursorY AS USHORT
END TYPE

CONST GFX_NULL = -1

DIM SHARED DOSBlock AS USHORT, DOSBlockAddr AS UINTEGER, regs AS __dpmi_regs
DIM SHARED VBEI AS VbeInfoBlock, VMDI AS ModeInfoBlock, CurNum%, ModeList(1 TO 256) AS USHORT
DIM SHARED TmpUI AS UINTEGER, UseSelector%, CSSelector%, CSAlias%, MouseImg AS ANY PTR, MouseSav AS ANY PTR
DIM SHARED RunCatArr(1 TO 100, 1 TO 4) AS ANY PTR, StayCatArr(1 TO 100) AS ANY PTR
DIM SHARED RGBPal(0 TO 255) AS PaletteType

'$STATIC
DIM SHARED MouseDataStart AS BYTE
DIM SHARED CallBackRegs AS __dpmi_regs, CallBackAddr AS __dpmi_raddr
DIM SHARED MouseHandlerData AS MouseHandlerDataType
DIM SHARED MouseDataEnd AS BYTE

MouseCursor:
DATA 0,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
DATA 0,0,2,2,2,2,2,2,2,2,2,2,2,2,2,2
DATA 0,1,0,2,2,2,2,2,2,2,2,2,2,2,2,2
DATA 0,1,1,0,2,2,2,2,2,2,2,2,2,2,2,2
DATA 0,1,1,1,0,2,2,2,2,2,2,2,2,2,2,2
DATA 0,1,1,1,1,0,2,2,2,2,2,2,2,2,2,2
DATA 0,1,1,1,1,1,0,2,2,2,2,2,2,2,2,2
DATA 0,1,1,1,1,1,1,0,2,2,2,2,2,2,2,2
DATA 0,1,1,1,1,1,1,1,0,2,2,2,2,2,2,2
DATA 0,1,1,1,1,1,0,0,0,2,2,2,2,2,2,2
DATA 0,1,0,0,1,1,0,2,2,2,2,2,2,2,2,2
DATA 0,0,2,2,0,1,1,0,2,2,2,2,2,2,2,2
DATA 2,2,2,2,0,1,1,0,2,2,2,2,2,2,2,2
DATA 2,2,2,2,0,0,0,0,2,2,2,2,2,2,2,2
DATA 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
DATA 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2

SUB MouseSelectorStore
'В теле этой процедуры будет храниться селектор сегмента данных программы
A% = 2 * B%
B% = 2 * A%
END SUB

SUB MouseHandler NAKED ()
'При вызове прерывания запрещены
'CS:EIP = указывает на эту процедуру
'DS:ESI = указывает на стек реального режима
'ES:EDI = указывает на структуру со значениями регистров при вызове (CallBackRegs)
'SS:ESP = фиксированный стек защищенного режима
ASM
 pusha
 push ds
 push es
 'Имитируем возврат из процедуры обработчика дальним возвратом (RETF) реального режима
 mov eax, [esi] 'выбираем из стека реального режима адрес возврата
 mov es:[edi + OffsetOf(__dpmi_regs, x.ip)], eax 'заносим адрес возврата в регистровую пару CS:IP
 add word ptr es:[edi + OffsetOf(__dpmi_regs, x.sp)], 4 'удаляем адрес возврата из стека реального режима
 lea ebx, [MouseSelectorStore]
 mov ds, word ptr cs:[ebx] 'восстанавливаем селектор сегмента данных
 lea ebx, [MouseHandlerData] 'в ebx будет храниться адрес области памяти для сохранения
 'сохраняем новое состояние мыши
 mov ax, word ptr es:[edi + OffsetOf(__dpmi_regs, x.bx)]
 mov [ebx + OffsetOf(MouseHandlerDataType, ButtonStatus)], ax
 mov ax, word ptr es:[edi + OffsetOf(__dpmi_regs, x.cx)]
 mov [ebx + OffsetOf(MouseHandlerDataType, MouseCursorX)], ax
 mov ax, word ptr es:[edi + OffsetOf(__dpmi_regs, x.dx)]
 mov [ebx + OffsetOf(MouseHandlerDataType, MouseCursorY)], ax
 mov cx, 6 'в cx номер текущего бита маски событий
LoopHandleEvent:
 bt word ptr es:[edi + OffsetOf(__dpmi_regs, x.ax)], cx
 jnc EventNotHappen
 cmp cx, 5
 je EventPressButton
 cmp cx, 3
 je EventPressButton
 cmp cx, 1
 je EventPressButton
 or cx, cx
 jnz EventNotHappen
 'увеличиваем счетчик событий перемещения курсора
 inc dword ptr [ebx + OffsetOf(MouseHandlerDataType, MouseCursorMoved)]
 jmp EventNotHappen
EventPressButton:
 'увеличиваем счетчик событий нажатия
 inc dword ptr [ebx + OffsetOf(MouseHandlerDataType, ButtonPressed)]
EventNotHappen:
 sub cx, 1
 jnc LoopHandleEvent 'еще не все значащие биты проверены
 pop es
 pop ds
 popa
 iret
END ASM
END SUB

FUNCTION BMPLoad (FileName$) AS ANY PTR
DIM img AS ANY PTR
'' open BMP file
ERR = 0: BMPLoad = NULL
FileNum% = FREEFILE
OPEN FileName$ FOR BINARY AS #FileNum%
IF ERR <> 0 THEN EXIT FUNCTION
'' retrieve BMP dimensions
GET #FileNum%, 19, bmpwidth&
GET #FileNum%, 23, bmpheight&
CLOSE #FileNum%
'' create image with BMP dimensions
img = IMAGECREATE(bmpwidth&, ABS(bmpheight&), 0, 8)
IF img = NULL THEN EXIT FUNCTION
'' load BMP file into image buffer
BLOAD FileName$, img
IF ERR <> 0 THEN IMAGEDESTROY(img): EXIT FUNCTION
BMPLoad = img
END FUNCTION

FUNCTION MyTimer&
DIM BIOSTimer AS UINTEGER
DOSMEMGET &H46C, 4, @BIOSTimer 'read BIOS timer counter
MyTimer& = BIOSTimer AND &H7FFFFFFF
END FUNCTION

FUNCTION AllocDOSMem (BYVAL NeedSize%) AS USHORT
ParSize% = NeedSize% \ 16
IF (NeedSize% MOD 16) <> 0 THEN ParSize% = ParSize% + 1
IF (ParSize% > 65535) OR (ParSize% <= 0) THEN AllocDOSMem = 0: EXIT FUNCTION
regs.x.ax = &H4800: regs.x.bx = ParSize%: __dpmi_int(&H21, @regs)
IF BIT(regs.x.flags, 0) THEN AllocDOSMem = 0: ERR = 255: EXIT FUNCTION
ERR = 0: AllocDOSMem = regs.x.ax 'сегмент выделенного блока
END FUNCTION

FUNCTION FreeDOSMem (BYVAL BlockSegm AS USHORT) AS USHORT
regs.x.ax = &H4900: regs.x.es = BlockSegm: __dpmi_int(&H21, @regs)
FreeDOSMem = BIT(regs.x.flags, 0)
END FUNCTION

FUNCTION VSScreenList (NDepth% = 0) AS UINTEGER
STATIC CurDepth%
VSScreenList = 0
IF NDepth% > 0 THEN 'это первое обращение - получаем список доступных режимов
 CurDepth% = 0: CurNum% = 0: tmp$ = "VBE2"
 DOSMEMPUT STRPTR(tmp$), 4, DOSBlockAddr
 regs.x.ax = &H4F00: regs.x.es = DOSBlock: regs.x.di = 0: __dpmi_int(&H10, @regs)
 IF regs.x.ax <> &H4F THEN ERR = 254: EXIT FUNCTION
 DOSMEMGET DOSBlockAddr, LEN(VBEI), @VBEI
 IF VBEI.VbeSignature <> CVL("VESA") THEN ERR = 253: EXIT FUNCTION 'нет верной сигнатуры
 IF VBEI.VbeVersion < &H200 THEN ERR = 252: EXIT FUNCTION 'версия VESA меньше 2.0 - линейный кадровый буфер не поддерживается
 'Считываем список доступных видеорежимов
 DOSMEMGET (CUINT(VBEI.VideoModePtr.RSegm) SHL 4) + VBEI.VideoModePtr.ROffs, 512, @ModeList(1)
 CurDepth% = NDepth%
END IF
'Процедура не вызывалась ранее с явным указанием NDepth% или при построении списка режимов произошла ошибка
IF CurDepth% <= 0 THEN ERR = 251: EXIT FUNCTION
DO
 CurNum% = CurNum% + 1
 IF (CurNum% < LBOUND(ModeList)) OR (CurNum% > UBOUND(ModeList)) THEN EXIT DO
 IF ModeList(CurNum%) = -1 THEN CurDepth% = 0: EXIT DO 'список доступных видеорежимов закончен
 'получим информацию о текущем видеорежиме
 regs.x.ax = &H4F01: regs.x.cx = ModeList(CurNum%)
 regs.x.es = DOSBlock: regs.x.di = 0: __dpmi_int(&H10, @regs)
 IF regs.x.ax <> &H4F THEN CONTINUE DO
 DOSMEMGET DOSBlockAddr, LEN(VMDI), @VMDI 'получаем блок информации о режиме
 'режим не поддерживается или он неграфический - не подходит
 IF NOT (BIT(VMDI.ModeAttributes, 0) AND BIT(VMDI.ModeAttributes, 0)) THEN CONTINUE DO
 'недоступен режим с линейным кадровым буфером
 IF (NOT BIT(VMDI.ModeAttributes, 7)) OR (VMDI.PhysBasePtr = 0) THEN CONTINUE DO
 'неподходящая модель цветности (поддерживаются только упаук ованные пиксели или "прямой цвет")
 IF (VMDI.MemoryModel <> 4) AND (VMDI.MemoryModel <> 6) THEN CONTINUE DO
 IF (VMDI.NumberOfBanks <> 1) OR (VMDI.NumberOfPlanes <> 1) THEN CONTINUE DO
 'количество разрядов цветности не совпадает с искомым
 IF VMDI.BitsPerPixel <> CurDepth% THEN CONTINUE DO
 VSScreenList = (CUINT(VMDI.XResolution) SHL 16) + VMDI.YResolution: EXIT DO
LOOP
IF (CurNum% < LBOUND(ModeList)) OR (CurNum% > UBOUND(ModeList)) THEN CurDepth% = 0 'список доступных видеорежимов закончен
ERR = 0
END FUNCTION

SUB VSScreenRes (NWidth%, NHeight%, NDepth%)
DIM VModeParam AS UINTEGER, minfo as __dpmi_meminfo, UseSize AS UINTEGER
VModeParam = VSScreenList(NDepth%)
DO UNTIL VModeParam = 0
 IF (HiWord(VModeParam) = NWidth%) AND (LoWord(VModeParam) = NHeight%) THEN EXIT DO
 VModeParam = VSScreenList
LOOP
IF (HiWord(VModeParam) <> NWidth%) OR (LoWord(VModeParam) <> NHeight%) THEN
 ERR = 255: EXIT SUB 'подходящего под требования режима не найдено
END IF
'Требуемый режим найден - его номер ModeList(CurNum%). Блок VMDI также уже заполнен.
UseSelector% = __dpmi_allocate_ldt_descriptors(1)
IF UseSelector% = 0 THEN ERR = 254: EXIT SUB
'Создаем дескриптор для обращения к кадровому буферу
UseSize = VMDI.BytesPerScanLine * VMDI.YResolution
IF (UseSize MOD &H1000) <> 0 THEN UseSize = ((UseSize \ &H1000) + 1) * &H1000 'выравниваем размер по целым страницам
minfo.address = VMDI.PhysBasePtr: minfo.size = UseSize
IF __dpmi_physical_address_mapping (@minfo) <> 0 THEN
 ERR = 253: EXIT SUB 'не удалось картировать видеобуфер в линейное адресное пространство
END IF
IF __dpmi_set_segment_base_address(UseSelector%, minfo.address) <> 0 THEN
 ERR = 252: EXIT SUB 'ошибка установки базового адреса
END IF
IF __dpmi_set_segment_limit(UseSelector%, UseSize - 1) <> 0 THEN
 ERR = 251: EXIT SUB 'ошибка установки предела сегмента
END IF
'Видеобуфер к работе подготовлен, соответствующий ему селектор настроен
'Теперь устанавливаем собственно сам графический режим
regs.x.ax = &H4F02: regs.x.bx = BITSET(ModeList(CurNum%), 14): __dpmi_int(&H10, @regs) 'режим с плоским буфером
IF regs.x.ax <> &H4F THEN ERR = 250: EXIT SUB
IF NDepth% = 8 THEN 'для 8-битовых видеорежимов установим стандартную RGB332 палитру
 FOR r% = 0 TO 255 'заполняем палитру
  WITH RGBPal(r%)
   .RedByte = (63 / 7) * ((r% \ 32) AND 7)
   .GreenByte = (63 / 7) * ((r% \ 4) AND 7)
   .BlueByte = (63 / 3) * (r% AND 3)
  END WITH
 NEXT r%
 regs.x.ax = &H4F08: regs.x.bx = &H600: __dpmi_int(&H10, @regs) 'установим размер палитры в 6 бит на канал
 DOSMEMPUT @RGBPal(0), 256 * SIZEOF(RGBPal(0)), DOSBlockAddr
 regs.x.ax = &H4F09: regs.x.bx = 0: regs.x.cx = 256: regs.x.dx = 0
 regs.x.es = DOSBlock: regs.x.di = 0: __dpmi_int(&H10, @regs) 'передаем данные палитры оборудованию
END IF
ERR = 0
END SUB

SUB VSScreenReset ()
'Освобождаем селектор и устанавливаем стандартный текстовый режим
IF UseSelector% <> 0 THEN
 __dpmi_free_ldt_descriptor(UseSelector%): UseSelector% = 0
END IF
regs.x.ax = 3: __dpmi_int(&H10, @regs) 'режим 3 - стандартный текстовый режим 80x25
END SUB

SUB VSPCopy
DIM ScrPtr AS ANY PTR
'Процедура переносит содержимое экранного буфера графической библиотеки в видеопамять
SCREENINFO , RowCount%, , , RowLength%: ScrPtr = SCREENPTR
IF VMDI.BytesPerScanLine < RowLength% THEN
 MoveLength% = VMDI.BytesPerScanLine
ELSE
 MoveLength% = RowLength%
END IF
SCREENLOCK
ASM
 push es
 pusha
 cld
 mov eax, [ScrPtr] 'хранилище начального адреса текущей строки буфера графической библиотеки
 mov ebx, [RowCount] 'количество строк, которое еще не перенесено
 xor edx, edx 'хранилище начального адреса текущей строки видеопамяти
 mov es, word ptr [UseSelector]
RowLoopMove:
 mov esi, eax
 mov edi, edx
 mov ecx, [MoveLength]
 rep movsb
 add eax, [RowLength]
 movzx ecx, word ptr [VMDI + OffsetOf(ModeInfoBlock, BytesPerScanLine)]
 add edx, ecx
 dec ebx
 jnz RowLoopMove 'еще не все строки перенесены
 popa
 pop es
END ASM
SCREENUNLOCK
END SUB

SUB GetMouseSav (UMouseSav AS ANY PTR, CurCursorX%, CurCursorY%)
CALL IMAGEINFO(UMouseSav, MouseWidth%, MouseHeight%)
SCREENINFO ScrWidth%, ScrHeight%
HiX% = CurCursorX%: HiY% = CurCursorY%
LoX% = HiX% + MouseWidth% - 1: LoY% = HiY% + MouseHeight% - 1
IF HiX% < 0 THEN HiX% = 0
IF HiY% < 0 THEN HiY% = 0
IF LoX% >= ScrWidth% THEN LoX% = ScrWidth% - 1
IF LoY% >= ScrHeight% THEN LoY% = ScrHeight% - 1
IF (HiX% > LoX%) OR (HiY% > LoY%) THEN EXIT SUB
GET (HiX%, HiY%) - (LoX%, LoY%), UMouseSav
END SUB

ErrMess$ = "": DOSBlock = AllocDOSMem(4096) 'выделяем блок нижней памяти в 4к
DOSBlockAddr = CUINT(DOSBlock) SHL 4 'линейный адрес выделенного блока
regs.x.ax = 0: __dpmi_int(&H33, @regs)
IF regs.x.ax <> &HFFFF THEN ErrMess$ = "Мышь не найдена": GOTO ImmExit5
IF _go32_dpmi_lock_data(@MouseDataStart, @MouseDataEnd - @MouseDataStart) <> 0 THEN
 'не удалось зафиксировать область данных обработчика событий мыши
 ErrMess$ = "Ошибка при фиксации данных обработчика событий мыши": GOTO ImmExit5
END IF
IF _go32_dpmi_lock_code(@MouseSelectorStore, CAST(BYTE PTR, @BMPLoad) - CAST(BYTE PTR, @MouseSelectorStore)) <> 0 THEN
 ErrMess$ = "Ошибка при фиксации кода обработчика событий мыши": GOTO ImmExit5
END IF
CSSelector% = _my_cs: CSAlias% = 0
CSAlias% = __dpmi_create_alias_descriptor(CSSelector%)
IF CSAlias% = 0 THEN
 ErrMess$ = "Не удалось создать псевдоним селектора сегмента кода": GOTO ImmExit5
END IF
ASM
 push es
 pusha
 mov es, word ptr [CSAlias]
 lea ebx, [MouseSelectorStore]
 mov word ptr es:[ebx], ds
 popa
 pop es
END ASM
IF __dpmi_allocate_real_mode_callback(@MouseHandler, @CallBackRegs, @CallBackAddr) <> 0 THEN
 ErrMess$ = "Не удалось выделить звонковую точку": GOTO ImmExit4
END IF
CALL VSScreenRes(640, 480, 8) 'режим 640x480x8
IF ERR <> 0 THEN
 r% = ERR: ErrMess$ = "Ошибка при вызове ScreenRes -" + STR$(r%): GOTO ImmExit3
END IF
SCREEN 18, 8, , GFX_NULL 'режим 640x480x8 без использования физического видеооборудования
MouseImg = IMAGECREATE(16, 16, 0, 8): MouseSav = IMAGECREATE(16, 16, 0, 8)
IF (MouseImg = NULL) OR (MouseSav = NULL) THEN ErrMess$ = "Ошибка при создании курсора мыши": GOTO ImmExit2
RESTORE MouseCursor
FOR r% = 0 TO 15 'создаем изображение курсора мыши
 FOR k% = 0 TO 15
  READ PictVal%
  SELECT CASE PictVal%
   CASE 1: PSET MouseImg, (k%, r%), 255 'светлые точки
   CASE 2: PSET MouseImg, (k%, r%), 0 'прозрачные точки
   CASE ELSE: PSET MouseImg, (k%, r%), 1 'темные точки
  END SELECT
NEXT k%, r%
c$ = DIR$("RUNCAT*.BMP"): RunCatCount% = 0: ErrMess$ = "Ошибка при загрузке анимации"
WHILE (LEN(c$) > 0) AND (RunCatCount% < UBOUND(RunCatArr))
 RunCatCount% = RunCatCount% + 1: RunCatArr(RunCatCount%, 1) = BMPLoad(c$)
 IF RunCatArr(RunCatCount%, 1) = NULL THEN GOTO ImmExit2
 'Очередная картинка бегущего кота загружена успешно.
 'Положим, что в исходном виде кот бежит слева направо. Получим котов, бегущих
 'справа налево, сверху вниз и снизу вверх.
 'Отразим зеркально кота для получения бега справа налево
 'и повернем на 90 градусов для получения бега сверху вниз.
 CALL IMAGEINFO(RunCatArr(RunCatCount%, 1), CatWidth%, CatHeight%)
 RunCatArr(RunCatCount%, 2) = IMAGECREATE(CatWidth%, CatHeight%, 0, 8)
 IF RunCatArr(RunCatCount%, 2) = NULL THEN GOTO ImmExit2
 RunCatArr(RunCatCount%, 3) = IMAGECREATE(CatHeight%, CatWidth%, 0, 8)
 IF RunCatArr(RunCatCount%, 3) = NULL THEN GOTO ImmExit2
 RunCatArr(RunCatCount%, 4) = IMAGECREATE(CatHeight%, CatWidth%, 0, 8)
 IF RunCatArr(RunCatCount%, 4) = NULL THEN GOTO ImmExit2
 FOR r% = 0 TO CatHeight% - 1
  FOR k% = 0 TO CatWidth% - 1
   PictVal% = POINT(k%, r%, RunCatArr(RunCatCount%, 1))
   PSET RunCatArr(RunCatCount%, 2), (CatWidth% - k% - 1, r%), PictVal%
   PSET RunCatArr(RunCatCount%, 3), (r%, k%), PictVal%
   PSET RunCatArr(RunCatCount%, 4), (r%, CatWidth% - k% - 1), PictVal%
 NEXT k%, r%
 c$ = DIR$
WEND
c$ = DIR$("STPCAT*.BMP"): StayCatCount% = 0
WHILE (LEN(c$) > 0) AND (StayCatCount% < UBOUND(StayCatArr))
 StayCatCount% = StayCatCount% + 1: StayCatArr(StayCatCount%) = BMPLoad(c$)
 IF StayCatArr(StayCatCount%) = NULL THEN GOTO ImmExit2
 c$ = DIR$
WEND
IF (RunCatCount% < 1) OR (StayCatCount% < 1) THEN ErrMess$ = "Не найдено изображений анимации": GOTO ImmExit2
ErrMess$ = ""
'скрываем курсор мыши
regs.x.ax = 2: __dpmi_int(&H33, @regs)
'устанавливаем диапазон изменения координат мыши
regs.x.ax = 7: regs.x.cx = 0: regs.x.dx = 639: __dpmi_int(&H33, @regs)
regs.x.ax = 8: regs.x.cx = 0: regs.x.dx = 479: __dpmi_int(&H33, @regs)
'устанавливаем обработчик событий мыши
regs.x.ax = &HC: regs.x.cx = &H7F: regs.x.es = CallBackAddr.segment
regs.x.dx = CallBackAddr.offset16: __dpmi_int(&H33, @regs)
'основной цикл
AnimeMouseX% = 0: AnimeMouseY% = 0: LastAnimeTimer& = MyTimer&: AnimeMode% = 0
DO
 IF kbhit <> 0 THEN IF getch = 27 THEN EXIT DO
 IF MouseHandlerData.MouseCursorMoved <> 0 THEN MouseHandlerData.MouseCursorMoved = 0
 IF MouseHandlerData.ButtonPressed <> 0 THEN MouseHandlerData.ButtonPressed = 0
 IF AnimeMode% = 0 THEN 'если анимация не включена, проверим, требуется ли ее включать
  IF (ABS(MouseHandlerData.MouseCursorX - AnimeMouseX%) > 3) OR (ABS(MouseHandlerData.MouseCursorY - AnimeMouseY%) > 3) THEN
   'положение кота сильно отличается от текущего положения мыши - включаем анимацию бегущего кота
   AnimeMode% = 1: CurAnimeCount% = 0
  ELSE
   'Если кот находится там же, где мышь, и нажата кнопка мыши - включаем анимацию стоящего кота
   IF (MouseHandlerData.ButtonStatus AND 3) <> 0 THEN AnimeMode% = 2: CurAnimeCount% = 0
  END IF
 END IF
 IF (AnimeMode% <> 0) AND (MyTimer& <> LastAnimeTimer&) THEN 'перерисовываем экран с очередным кадром анимации
  CurAnimeCount% = CurAnimeCount% + 1
  IF AnimeMode% = 1 THEN
   IF CurAnimeCount% > RunCatCount% THEN CurAnimeCount% = 1
   CLS
   IF MouseHandlerData.MouseCursorX > AnimeMouseX% THEN
    AnimeMouseX% = AnimeMouseX% + 1 'бежим слева направо
    PUT (AnimeMouseX% - CatWidth%, AnimeMouseY%), RunCatArr(CurAnimeCount%, 1), PSET
   ELSEIF MouseHandlerData.MouseCursorX < AnimeMouseX% THEN
    AnimeMouseX% = AnimeMouseX% - 1 'бежим справа налево
    PUT (AnimeMouseX%, AnimeMouseY%), RunCatArr(CurAnimeCount%, 2), PSET
   ELSEIF MouseHandlerData.MouseCursorY > AnimeMouseY% THEN
    AnimeMouseY% = AnimeMouseY% + 1 'бежим сверху вниз
    PUT (AnimeMouseX%, AnimeMouseY% - CatHeight%), RunCatArr(CurAnimeCount%, 3), PSET
   ELSEIF MouseHandlerData.MouseCursorY < AnimeMouseY% THEN
    AnimeMouseY% = AnimeMouseY% - 1 'бежим снизу вверх
    PUT (AnimeMouseX%, AnimeMouseY%), RunCatArr(CurAnimeCount%, 4), PSET
   ELSE 'Если кошка догнала мышку
    AnimeMode% = 0: PUT (AnimeMouseX%, AnimeMouseY%), StayCatArr(1), PSET
   END IF
  ELSEIF AnimeMode% = 2 THEN
   IF (MouseHandlerData.ButtonStatus AND 3) = 0 THEN 'Проверим, не отпущена ли кнопка мыши
    AnimeMode% = 0: CurAnimeCount% = 1 'кнопка отпущена - отключаем анимацию неподвижного кота
   ELSE 'кнопка все еще нажата - выводим следующий кадр анимации
    IF CurAnimeCount% > StayCatCount% THEN CurAnimeCount% = 1
   END IF
   CLS: PUT (AnimeMouseX%, AnimeMouseY%), StayCatArr(CurAnimeCount%), PSET
  END IF
  CurMouseCursorX% = MouseHandlerData.MouseCursorX: CurMouseCursorY% = MouseHandlerData.MouseCursorY
  CALL GetMouseSav(MouseSav, CurMouseCursorX%, CurMouseCursorY%)
  PUT (CurMouseCursorX%, CurMouseCursorY%), MouseImg, TRANS 'выведем курсор мыши
  CALL VSPCopy: LastAnimeTimer& = MyTimer&
 ELSE 'перерисовывать экран не нужно - просто обработаем перемещение мыши
  IF (MouseHandlerData.MouseCursorX <> CurMouseCursorX%) OR (MouseHandlerData.MouseCursorY <> CurMouseCursorY%) THEN
   PUT (CurMouseCursorX%, CurMouseCursorY%), MouseSav, PSET 'удалим курсор по старому положению
   CurMouseCursorX% = MouseHandlerData.MouseCursorX: CurMouseCursorY% = MouseHandlerData.MouseCursorY
   CALL GetMouseSav(MouseSav, CurMouseCursorX%, CurMouseCursorY%)
   PUT (CurMouseCursorX%, CurMouseCursorY%), MouseImg, TRANS: CALL VSPCopy 'нарисуем курсор по новому положению
  END IF
 END IF
LOOP
WHILE kbhit <> 0: getch: WEND
ImmExit2:
'удаляем обработчик событий мыши
regs.x.ax = &HC: regs.x.cx = 0: regs.x.es = 0: regs.x.ds = 0: __dpmi_int(&H33, @regs)
'удаляем загруженные картинки анимации
FOR r% = 1 TO RunCatCount%
 FOR k% = 1 TO 4
  IF RunCatArr(r%, k%) <> NULL THEN IMAGEDESTROY(RunCatArr(r%, k%))
NEXT k%, r%
FOR r% = 1 TO StayCatCount%
 IF StayCatArr(r%) <> NULL THEN IMAGEDESTROY(StayCatArr(r%))
NEXT r%
'удаляем курсор мыши
IF MouseImg <> NULL THEN IMAGEDESTROY(MouseImg)
CALL VSScreenReset: SCREEN 0
ImmExit3:
__dpmi_free_real_mode_callback(@CallBackAddr)
ImmExit4:
__dpmi_free_ldt_descriptor(CSAlias%)
ImmExit5:
DOSBlock = FreeDOSMem(DOSBlock)
IF LEN(ErrMess$) > 0 THEN PRINT ErrMess$
END
The program combines the using of the standard graphic FreeBASIC library, direct VESA access and obtaining mouse events.

See how it works. If something is unclear, ask questions here.

The program is absolutely foolish, but for demonstration quite approaches.
Unpack all files from archive in one directory and start the "catmouse.exe" file.
Move mouse a little and look what will occur.
Also you can press mouse buttons.

Download link: http://tempfile.ru/file/2902910
MichaelW
Posts: 3500
Joined: May 16, 2006 22:34
Location: USA

Re: How do you do a real mode callback for the mouse?

Post by MichaelW »

This is a test of the Interrupt 15h PS/2 mouse interface. It simply displays the mouse packet data. This data could be used as the input for a mouse driver, integral to your program. The integral driver could basically use the packet data to maintain a cursor position and button status, and manage a mouse cursor.

My test system has a Phoenix BIOS dated 04/10/98, and a PS/2 Microsoft Wheel Mouse attached, and the program works as expected.

All 4 of my hardware references agree that the Z (wheel) data is reserved, and in my tests it was always zero. I had problems determining the correct packet size, but eventually arrived at a size of 3 bytes, based on the data here, and it worked correctly (where a packet size of 4 did not), but note that the correct value depends on the mouse.

Code: Select all

''=============================================================================
'' This is a test of the Interrupt 15h PS/2 mouse interface.
''=============================================================================

''---------------------------------------------------------
'' A simplified version of the RM register data structure:
''---------------------------------------------------------

type DPMIREGS
    edi   as uinteger   '' 0
    esi   as uinteger   '' 4
    ebp   as uinteger   '' 8
    res   as uinteger   '' 12
    ebx   as uinteger   '' 16
    edx   as uinteger   '' 20
    ecx   as uinteger   '' 24
    eax   as uinteger   '' 28
    flags as ushort     '' 32
    es    as ushort     '' 34
    ds    as ushort     '' 36
    fs    as ushort     '' 38
    gs    as ushort     '' 40
    ip    as ushort     '' 42
    cs    as ushort     '' 44
    sp    as ushort     '' 46
    ss    as ushort     '' 48
end type

dim shared as DPMIREGS g_regs

''------------------------------------------------------------------------
'' These defines make it easier to access the members from assembly code.
''------------------------------------------------------------------------

#define REDI    offsetof(DPMIREGS,edi)
#define RESI    offsetof(DPMIREGS,esi)
#define REBP    offsetof(DPMIREGS,ebp)
#define REBX    offsetof(DPMIREGS,ebx)
#define REDX    offsetof(DPMIREGS,edx)
#define RECX    offsetof(DPMIREGS,ecx)
#define REAX    offsetof(DPMIREGS,eax)
#define RFLAGS  offsetof(DPMIREGS,flags)
#define RES     offsetof(DPMIREGS,es)
#define RDS     offsetof(DPMIREGS,ds)
#define RFS     offsetof(DPMIREGS,fs)
#define RGS     offsetof(DPMIREGS,gs)
#define RIP     offsetof(DPMIREGS,ip)
#define RCS     offsetof(DPMIREGS,cs)
#define RSP     offsetof(DPMIREGS,sp)
#define RSS     offsetof(DPMIREGS,ss)

''-------------------------------------------------
'' These globals store the mouse packet data and a
'' flag to indicate that the data has changed.
''-------------------------------------------------

dim shared as ushort g_zdata, g_ydata, g_xdata, g_status
dim shared as integer g_event

''=============================================================================
'' This is our PM callback procedure.
'' At entry:
''    Interrupts disabled
''    CS:(E)IP = selector:offset specified in call to function 0303H
''    DS:(E)SI = selector:offset corresponding to RM SS:SP
''    ES:(E)DI = selector:offset of RM register data structure
''    SS:(E)SP = locked protected mode stack provided by DPMI host
''    All other registers undefined
'' Note that ES has a valid selector for the program data segment.
''=============================================================================

sub CallBack naked()

    asm

        ''-----------------------------------------------------------------
        '' Store the packet data to our global variables and set the flag.
        ''-----------------------------------------------------------------

        mov ax, [esi+4]           '' z
        mov es:[g_zdata], ax
        mov ax, [esi+6]           '' y
        mov es:[g_ydata], ax
        mov ax, [esi+8]           '' x
        mov es:[g_xdata], ax
        mov ax, [esi+10]          '' status
        mov es:[g_status], ax
        mov DWORD PTR es:[g_event], 1

        ''-----------------------------------------------------------
        '' Before returning to the caller we need to copy the return
        '' address from the RM stack to the CS and IP members of the
        '' register data structure. The return address was the last
        '' value pushed onto the RM stack, the return CS first and
        '' the return IP last, so we get the IP member from DS:ESI
        '' and the CS member from DS:ESI+2.
        ''-----------------------------------------------------------

        mov ax, [esi]
        mov es:[edi+RIP], ax
        mov ax, [esi+2]
        mov es:[edi+RCS], ax

        iret

    end asm

end sub

''=============================================================================

dim shared as ushort callSegment, callOffset

sub _constructor() constructor

    dim as integer fError
    dim as ubyte statusByte, resolution, sampleRate

    asm

        ''---------------------------------
        '' Allocate a RM callback address.
        ''---------------------------------

        push ds
        push es
        mov ax, 0x303
        push ds
        pop es
        push cs
        pop ds
        mov esi, OFFSET CallBack
        mov edi, OFFSET g_regs
        int 0x31
        pop es
        pop ds
        jc  error
        mov [callSegment], cx
        mov [callOffset], dx

        ''------------------------------------------------------------------
        '' Set the mouse handler address to the allocated callback address.
        '' Function c207h requires that the caller load ES with the RM
        '' segment address of the callback. Since loading a RM segment
        '' address into a segment register from PM would trigger an
        '' exception, the function must be called via the DPMI Simulate
        '' Real Mode Interrupt function.
        ''------------------------------------------------------------------

        ''--------------------------------------------------------------
        '' Load the required members of the RM register data structure.
        ''--------------------------------------------------------------

        mov eax, 0xc207
        mov [g_regs+REAX], eax
        mov ax, [callSegment]
        mov [g_regs+RES], ax
        movzx ebx, WORD PTR [callOffset]
        mov [g_regs+REBX], ebx

        ''----------------------------
        '' Simulate the RM interrupt.
        ''----------------------------

        mov ax, 0x300
        mov bx, 0x15
        xor cx, cx
        mov edi, OFFSET g_regs
        int 0x31
        mov ax, [g_regs+RFLAGS]
        test ax, 1
        jnz error

        ''----------------------------------------------------
        '' Initialize the mouse and set the data packet size.
        ''----------------------------------------------------

        mov ax, 0xc205
        mov bh, 3                 '' data packet size
        int 0x15
        jc  error

        ''-------------------
        '' Enable the mouse.
        ''-------------------

        mov ax, 0xc200
        mov bh, 1
        int 0x15
        jc  error
        jmp 0f

      error:

        inc DWORD PTR [fError]

      0:

    end asm

    if fError then
        print "ERROR"
    else
        asm
            mov ax, 0xc206
            xor bh, bh            '' return status
            int 0x15              '' assume no error
            mov [statusByte], bl
            mov [resolution], cl
            mov [sampleRate], dl
        end asm

        if statusByte and (2^6) then
            print "remote mode"
        else
            print "stream mode"
        end if

        if statusByte and (2^5) then
            print "enabled"
        else
            print "disabled"
        end if

        if statusByte and (2^4) then
            print "scaling = 2:1"
        else
            print "scaling = 1:1"
        end if

    end if

end sub

''=============================================================================

sub _destructor() destructor
    asm

        ''--------------------
        '' Disable the mouse.
        ''--------------------

        mov ax, 0xc200
        mov bh, 0
        int 0x15

        ''-------------------------------
        '' Free the RM callback address.
        ''-------------------------------

        mov ax, 0x304
        mov cx, [callSegment]
        mov dx, [callOffset]
        int 0x31

    end asm
end sub

''============================================================================

dim as string k

''-------------------------------------
'' Status bits:
'' bits8-15  reserved
'' bit7      1 = y value overflow
'' bit6      1 = x value overflow
'' bit5      1 = y value negative
'' bit4      1 = x value negative
'' bit3      reserved, set to 1
'' bit2      reserved, set to 0
'' bit1      1 = right button pressed
'' bit0      1 = left button pressed
''-------------------------------------

do
    if g_event then
        print g_zdata, g_ydata, g_xdata, bin(g_status,8)
        g_event = 0
    end if
    k = inkey
loop until k = chr(27)

sleep
I tested with 0.24.0 only.

I attempted a test on a second system, with an AMIBIOS from 2000 IIRC, and while the program initialized OK, it would hang if I did anything with the mouse. Based on information in my AMIBIOS reference, I suspect a problem with the keyboard controller or keyboard controller BIOS.

And I tried the method specified on the linked page to enable the wheel, and while the mouse returned the correct IDs, the wheel was not enabled.
lassar
Posts: 306
Joined: Jan 17, 2006 1:35

Re: How do you do a real mode callback for the mouse?

Post by lassar »

What about the way real mode callback is done in Matrox SVGA & Mouse with DJGPP

He definitely uses regular code instead of assembly in his callback handler.
lassar
Posts: 306
Joined: Jan 17, 2006 1:35

Re: How do you do a real mode callback for the mouse?

Post by lassar »

Thank you monochromator for reply to my post and helping me out with my code.

I managed to get GFX_NULL vesa mouse routines working.

Here is the link to it in tricks and tips.

Again I say thank you.
Post Reply