如何将闪烁的光标添加到图形视频模式?

问题描述

在VGA图形模式下,光标不会显示,但BIOS会跟踪其位置。对于每个可用的显示页,BIOS都会在光标保存区域中记录光标的列和行坐标(不一定是X和Y坐标),从线性地址0450h开始16个字节。有趣的事实:BIOS也不必要地更新CRT控制器的光标位置高光标位置低

自从开始在图形屏幕上运行的应用程序以来,就不得不创建自己的游标,因此我完全意识到,我也将不得不提供自己的游标。

尽管有一个小故障。显然,DOS希望用户在图形屏幕上进行操作时,能够在不借助任何光标的情况下编辑命令行!从应用程序调用DOS.BufferedInput function 0Ah也是如此。
那么如何在应用程序和命令提示符下将光标添加到图形视频模式呢?

(这是

解决方法

要在用户应用程序和DOS命令行中同时获得光标功能,解决方案是编写Terminate and Stay Resident(TSR)程序。而且,如果将其添加到AUTOEXEC.BAT中,则无需考虑安装它!
虽然是一个初步的关注。将游标强加于可能是多年以前编写的并且在没有游标存在的前提下构建的应用程序上会很有害。该应用程序将提供自己的光标。

此光标驱动器(TSR)的设计选择

输入时可用的光标。永久显示光标是不希望的。当输出字符时,在屏幕上看到下划线拍摄毫无意义。当程序期望用户从键盘输入时,游标是有用且必要的。
光标驱动程序主要集中在键盘BIOS上,更具体地说,功能为00h(10h,20h)GetKeystroke和01h(11h,21h)CheckForKeystroke。也不需要查看任何相关的DOS输入功能,因为最终这些功能将调用底层的键盘BIOS。在DOSBox中也是如此。
短语“输入时光标可用 ”无异于说在大多数情况下都应禁用光标(...)。然后,为了使光标在命令提示符下自动显示,驱动程序钩住了int28h中断,在输入过程中DOS会不断调用该中断。 int28h信号用作临时启用光标。此后不久,int08h信号将再次禁用光标。

忠实模仿文字视频光标。这意味着覆盖模式下划线,插入模式下半格。光标必须以大约2 Hz的频率闪烁。为了获得闪烁效果,驱动程序查看BIOS 18.2 Hz计时器。光标每四分之一秒改变一次相位(ON / OFF)。这个速率非常接近我们在文本视频模式下获得的速率。光标将根据位于线性地址0417h的BIOS.KeyboardFlags的第7位,在下划线(用于覆盖)和半单元格(用于插入)之间正确切换形状。光标驱动程序支持以下屏幕模式:13:320x200x4,14:640x200x4,15:640x350x2,16:640x350x4,17:640x480x1,18:640x480x4,19:320x200x8。

占地面积小。我愿意安装任何(有用的)TSR与TSR程序的大小成反比。我很高兴地说,这个TSR非常紧凑,只有624个字节,包括Memory Control Block(MCB)!当然要达到这个小尺寸,必须做出让步,导致一些瑕疵(偶尔有视觉残留)。选择完美的光标可能需要过多的内存。

  • 收回PSP 。这是一个三步过程。首先,安装程序将驻留代码下移到内存中,以覆盖大部分Program Segment Prefix(PSP),但要注意不要破坏前面的重要数据,其次是调用DOS.TerminateAndStayResident function 31h,然后是其余的字节。旧的PSP用作驱动程序的后台缓冲区。可以收回整个PSP,因为此驱动程序永远不必调用任何DOS函数!

  • 限制api 必要的api已添加到BIOS.GetModeInfo function 0Fh中,该api通常报告以下内容:视频模式编号,列数和活动显示页面。这些项目在AXBH寄存器中返回。添加的2个子功能使用相同的寄存器。子功能AX=0F01h EBX="CURS" EnableGraphicsCursor AX=0F02h EBX="CURS" DisableGraphicsCursor 期望在EBX寄存器中有一个签名,以便区分新旧。返回时,AX的未修改内容构成证明已安装驱动程序,因为普通的Video BIOS函数0Fh永远不会产生这些值!

  • 忘记速度,这是速度根本不会成为问题的场合之一。如果仔细检查源代码,您会发现许多速度上的低效率,例如使用Self Modifying Code和冗余保存许多VGA寄存器,但是由于我的光标对象非常小,几乎不需要随时间更改,没关系。

; ***************************************
; *  GraphicsCursor  v1.00  01/10/2020  *
; ***************************************
; Memory map:
; 0000h PixelBuffer     ; b7 Graphics mode      1=Yes 0=No
; 0040h CursorCtrl ---> ; b6 Cursor enable      1=Yes 0=No
; 0041h Code (first)    ; b5 Int28              1=Yes 0=No
; 025Dh Code (last)     ; b4 Cursor shown       1=Yes 0=No
CC=0040h
ZZ=0103h-0041h          ; Relocation factor

        ORG     256

        jmp     Start
; --------------------------------------
Modes   db                   00'0'01000b,01'0'01000b,01'1'01110b
        db      01'1'01110b,01'1'10000b,11'0'01000b
; --------------------------------------
New08:  and     byte [cs:CC],11011111b ; Clearing Int28
Old08:  jmp     08h*4:New08-ZZ
; --------------------------------------
New28:  or      byte [cs:CC],00100000b ; Setting Int28
Old28:  jmp     28h*4:New28-ZZ
; --------------------------------------
New10:  test    ah,ah
        jz      .SetVideoMode
        cmp     ebx,"CURS"
        jne     Old10
        cmp     ax,0F01h
        jb      Old10
        je      .EnableGraphicsCursor
        cmp     ax,0F02h
        ja      Old10
; - - - - - - - - - - - - - - - - - - -
.DisableGraphicsCursor:
        call    HideC
        and     byte [cs:CC],10011111b
        iret
.EnableGraphicsCursor:
        or      byte [cs:CC],01000000b
        iret
; - - - - - - - - - - - - - - - - - - -
.SetVideoMode:
        pushf
Old10_: call    0:0
TestG:  pusha
        push    ds
        xor     ax,ax
        mov     ds,ax
        mov     al,[0449h]             ; BIOS.CurrentDisplayMode
        push    cs
        pop     ds
        and     al,127
        sub     al,13                  ; Modes [0,12] are unsupported modes
        cmp     al,7
        jnb     .NOK                    ; Unsupported mode,AH=0
        mov     bx,Modes-ZZ
        xlatb                           ; -> AL is ModeInfo xx'y'zzzzzb
        aam     64
        mov     [Prep-ZZ+44],ah        ; {0=Mode13,1=Modes[14,18],3=Mode19}
        mov     [ShowC-ZZ+10],al       ; ModeInfo 00'y'zzzzzb
.OK:    mov     ah,10000000b
.NOK:   mov     [CC],ah                ; AH={0,128}
        pop     ds
        popa
        iret
; - - - - - - - - - - - - - - - - - - -
Old10:  jmp     10h*4:New10-ZZ
; --------------------------------------
New16:  cmp     byte [cs:CC],10100000b ; Gfx AND (Cursor enabled OR Int28) ?
        jb      Old16                   ; No
        push    ax                      ; (1)
        and     ah,11001111b           ; Function number
        cmp     ah,1
        ja      .Other                  ; Not in {00h,01h,10h,11h,20h,21h}
        pushf                           ; (2)
        push    ds                      ; (3)
        sti
        push    0
        pop     ds
        je      .CheckForKeystroke

.GetKeystroke:
.Loop:  test    byte [cs:CC],01100000b ; (Cursor enabled OR Int28) ?
        jz      .HideC                  ; No,Int28 fell off!
        test    byte [046Ch],00000100b ; BIOS.Timer CursorPhase
        jz      .OFF
.ON:    call    ShowC
        mov     ax,[041Ah]             ; BIOS.KeyboardBufferHead
        cmp     ax,[041Ch]             ; BIOS.KeyboardBufferTail
        je      .Loop                   ; No key waiting
.OFF:   call    HideC
        mov     ax,[041Ch]             ; BIOS.KeyboardBufferTail
        je      .Loop                   ; No key waiting
        jmp     .Done                   ; Key is available

.CheckForKeystroke:
        test    byte [046Ch],00000100b ; BIOS.Timer CursorPhase
        jz      .HideC
        call    ShowC
        jmp     .Done
.HideC: call    HideC

.Done:  pop     ds                      ; (3)
        popf                            ; (2)
.Other: pop     ax                      ; (1)
Old16:  jmp     16h*4:New16-ZZ
; --------------------------------------
; IN (ds=0) OUT ()
ShowC:  test    byte [cs:CC],00010000b
        jnz     .RET                    ; Already shown
        pusha
        mov     al,0                   ; SMC,ModeInfo 00'y'zzzzzb
        aam     32
        movzx   si,ah                  ; [0,1]
        inc     si                      ; Thickness {1,2,2}
        cbw                             ; CellHeight {8,14,16}
        cwd
        movzx   bx,byte [0462h]        ; BIOS.CurrentDisplayPage
        shl     bx,1
        mov     cx,[0450h+bx]          ; BIOS.CursorColumn
        xchg    dl,ch                  ; BIOS.CursorRow
        shl     cx,3                   ; -> X
        inc     dx
        imul    dx,ax                  ; -> Y (Below the matrix)
        test    byte [0417h],128       ; BIOS.InsertMode ?
        jz      .a                      ; No
        shr     ax,1                   ; Half-cell
        mov     si,ax
.a:     cmp     al,16
        jb      .b
        dec     dx
.b:     push    ds                      ; (1)
        push    cs
        pop     ds
        xor     di,di                  ; PixelBuffer
        mov     [HideC-ZZ+12],si       ; Thickness
        mov     [HideC-ZZ+15],dx       ; Y
        mov     [HideC-ZZ+18],cx       ; X
        mov     bl,7                   ; White
.c:     dec     dx
.d:     call    ReadPixel               ; -> AL
        mov     [di],al
        inc     di
        call    WritePixel
        inc     cx                      ; Next X
        test    cl,bl                  ; BL=7
        jnz     .d
        sub     cx,8
        dec     si
        jnz     .c
        call    ReadPixel               ; -> AL
        mov     [HideC-ZZ+26],al       ; CursorColor
        or      byte [CC],00010000b
        pop     ds                      ; (1)
        popa
.RET:   ret
; --------------------------------------
; IN () OUT ()
HideC:  test    byte [cs:CC],00010000b
        jz      .RET                    ; Currently not shown
        pusha
        xor     di,di                  ; PixelBuffer
        mov     si,Thickness
        mov     dx,Y
        mov     cx,X
; First see if our cursor is still there
        pusha                           ; (1)
.a:     dec     dx                      ; Next Y
.b:     call    ReadPixel               ; -> AL
        cmp     al,CursorColor
        jne     .c                      ; Not white
        inc     cx                      ; Next X
        test    cl,7
        jnz     .b
        sub     cx,8
        dec     si
        jnz     .a
.c:     popa                            ; (1)
        jnz     .f                      ; Impaired cursor: abandon restoration
; Restore background                    ;            and consider it is hidden
.d:     dec     dx                      ; Next Y
.e:     mov     bl,[cs:di]
        inc     di
        call    WritePixel
        inc     cx                      ; Next X
        test    cl,7
        jnz     .e
        sub     cx,8
        dec     si
        jnz     .d
.f:     and     byte [cs:CC],11101111b
        popa
.RET:   ret
; --------------------------------------
; IN (cx,dx) OUT (cx,dx=03CEh,ds:si) MOD (al,di)
Prep:   mov     si,cx                  ; X
        mov     di,dx                  ; Y
        push    cs
        pop     ds
        mov     dx,03CEh               ; -> DX is Graphics Controller
        in      al,dx                  ; Read Address register
        mov     [Rest-ZZ+13],al
        mov     al,8
        out     dx,al
        inc     dx
        in      al,dx                  ; Read BitMask register
        dec     dx
        mov     [Rest-ZZ+10],4
        out     dx,dx                  ; Read ReadMapSelect register
        dec     dx
        mov     [Rest-ZZ+6],5
        out     dx,dx                  ; Read Mode register
        mov     [Rest-ZZ+2],al

        imul    di,40                  ; Y
        shl     di,2                   ; SMC {0 is x40,1 is x80,3 is x320}
        mov     al,2
        cmp     [$-ZZ-3],al
        pushf                           ; (1) CF=0 mode 19,CF=1 other modes
        jnb     @f
        out     dx,al                  ; -> Mode register (mode 2)
        shr     si,3                   ; X
@@:     add     si,di
        push    0
        pop     ds
        add     si,[044Eh]             ; BIOS.StartCurrentPage
        push    0A000h
        pop     ds                      ; -> DS:SI is PixelAddress
        and     cx,7                   ; X Mod 8
        dec     dx                      ; -> DX=03CEh
        popf                            ; (1)
        ret
; --------------------------------------
; IN (cx,dx) OUT (al)
ReadPixel:
        pusha
        push    ds
        call    Prep                    ; -> CX DX=03CEh DS:SI CF (AL DI)
        jnc     .Is19
.Other: xor     cx,7                   ; -> CX is PixelBitNumber
        mov     bl,0
        mov     ax,0304h               ; Plane 3
@@:     out     dx,ax                  ; -> Read Map Select register
        bt      [si],cx
        rcl     bl,1
        dec     ah                      ; Plane 2 then 1 then 0
        jns     @b
        jmp     .Done
.Is19:  mov     bl,[si]
.Done:  mov     bp,sp
        mov     [bp+16],bl             ; pusha.AL
; ---   ---   ---   ---   ---   ---   --
Rest:   mov     ax,0005h               ; SMC,Original Mode register
        out     dx,ax
        mov     ax,0004h               ; SMC,Original ReadMapSelect register
        out     dx,0008h               ; SMC,Original BitMask register
        out     dx,00h                 ; SMC,Original Address register
        out     dx,al
        pop     ds
        popa
        ret
; --------------------------------------
; IN (bl,cx,dx) OUT ()
WritePixel:
        pusha
        push    ds
        call    Prep                    ; -> CX DX=03CEh DS:SI CF (AL DI)
        jnc     .Is19
.Other: mov     ax,8008h
        shr     ah,cl                  ; -> AH is PixelMask
        out     dx,ax                  ; -> BitMask register
        mov     cl,[si]                ; Dummy read
.Is19:  mov     [si],bl                ; Write color
        jmp     Rest
; --------------------------------------
        db      15 dup 0
; --------------------------------------
Start:  cld
; Showing copyright
        mov     dx,.Logo
        mov     ah,09h                 ; DOS.PrintString
        int     21h
; Searching installed copy of this program
        mov     dx,es                  ; Scanning memory below this program
        mov     bx,0051h               ; and above the BIOS vars
.Scan:  mov     ds,bx                  ; using a 14-byte signature
        mov     di,0103h
        mov     si,0041h
        mov     cx,14
        repe cmpsb
        je      .Found                  ; CF=0 means installed
        inc     bx
        cmp     bx,dx
        jb      .Scan
        stc                             ; CF=1 means not installed
.Found: mov     ds,dx
        pushf                           ; (1)
; Checking commandline
        mov     ecx,[0080h]
        cmp     cx,0D00h               ; C:\>CURSOR
        je      .Naked
.Text:  mov     dx,.Self
        mov     ah,09h                 ; DOS.PrintString
        int     21h
        mov     dx,.No
        popf                            ; (1a)
        jc      .Go                     ; Not installed
        cmp     ecx,0D3F2002h          ; C:\>CURSOR ?
        je      .Is
        mov     dx,.YesDo1
        mov     ax,0F01h
        cmp     ecx,0D312002h          ; C:\>CURSOR 1
        je      .Do
        mov     dx,.Help
        cmp     ecx,0D302002h          ; C:\>CURSOR 0
        jne     .Go
        mov     dx,.YesDo0
        mov     ax,0F02h
.Do:    mov     ebx,"CURS"
        int     10h                     ; -> AX=[0F01h,0F02h]
        jmp     .Go
.Is:    mov     es,bx                  ; -> ES=Segment TSR
        mov     dx,.YesIs0
        test    byte [es:CC],01000000b ; Cursor enabled ?
        jz      .Go
        mov     dx,.YesIs1
.Go:    jmp     .Quit_
; - - - - - - - - - - - - - - - - - - -
; Testing installed
.Naked: popf                            ; (1b)
        jnc     .Exist                  ; Already installed
; Hooking system timer,video BIOS,keyboard,and DOSOK
.New:   cli
        mov     bx,Old08+1
        call    ChangeIntVect           ; -> EAX
        mov     bx,Old10+1
        call    ChangeIntVect           ; -> EAX
        mov     [Old10_+1],eax
        mov     bx,Old16+1
        call    ChangeIntVect           ; -> EAX
        mov     bx,Old28+1
        call    ChangeIntVect           ; -> EAX
; Reclaiming space from the PSP
        mov     si,0103h
        mov     di,0041h
@@:     movsb
        cmp     si,Start
        jb      @b                      ; (*)
; Setting up some vars depending on current video mode
        mov     [$+8],cs
        pushf                           ; TestG ends with an 'iret'
        call    0:TestG-ZZ
        sti
; Freeing the environment
        mov     es,[002Ch]
        mov     ah,49h                 ; DOS.ReleaseMemory
        int     21h
; Ending program but keeping its TSR portion
        mov     dx,.OK_
        mov     ah,di                  ; (*)
        shr     dx,4
        mov     ax,3100h               ; DOS.TerminateAndStayResident
        int     21h
; - - - - - - - - - - - - - - - - - - -
; A subsequent invocation w/o parameter removes the TSR from memory
.Exist: mov     es,bx                  ; -> ES=Segment TSR
; Checking ownership interrupt vectors
        xor     ax,ax                  ; -> DS=Segment IVT
        mov     dx,.NOK
        mov     al,5                   ; 'Access denied'
        shl     ebx,16
        mov     bx,New08-ZZ
        cmp     [08h*4],ebx
        jne     .Quit
        mov     bx,New10-ZZ
        cmp     [10h*4],New16-ZZ
        cmp     [16h*4],New28-ZZ
        cmp     [28h*4],ebx
        jne     .Quit
; Unhooking interrupt vectors
        mov     eax,[es:Old08-ZZ+1]
        mov     [08h*4],eax
        mov     eax,[es:Old10-ZZ+1]
        mov     [10h*4],[es:Old16-ZZ+1]
        mov     [16h*4],[es:Old28-ZZ+1]
        mov     [28h*4],eax
; Taking ownership of the TSR memory
        mov     ax,es
        dec     ax
        mov     ds,ax
        mov     [0001h],cs             ; DOS.MCB.Owner
; Releasing the TSR memory
        mov     ah,49h                 ; DOS.ReleaseMemory
        int     21h                     ; -> AX CF
        jc      .Quit                   ; AL={7,9}
; Ending program
        mov     dx,.OK
.Quit_: mov     al,0                   ; 'OK'
.Quit:  push    cs
        pop     ds
        push    ax
        mov     ah,09h                 ; DOS.PrintString
        int     21h
        pop     ax
        mov     ah,4Ch                 ; DOS.Terminate AL={0,5,7,9}
        int     21h
; - - - - - - - - - - - - - - - - - - -
.Logo   db      'GraphicsCursor v1.00 (c) 2020 Sep Roland',13,10,'$'
.Self   db      'CURSOR is $'
.Help   db      'a driver that adds an input cursor to the',10
        db      'graphics modes: 13: 320x200x4,14: 640x200x4,15: 640x350x2',10
        db      ' 16: 640x350x4,17: 640x480x1,18: 640x480x4,19: 320x200x8',10
        db      'Use: CURSOR    (un)install driver',10
        db      '     CURSOR ?  report status',10
        db      '     CURSOR 1  enable cursor',10
        db      '     CURSOR 0  disable cursor','$'
.No     db      'currently not installed','$'
.YesIs0 db      'installed and currently disabled','$'
.YesIs1 db      'installed and currently enabled','$'
.YesDo0 db      'installed and now disabled','$'
.YesDo1 db      'installed and now enabled','$'
.OK_    db      'CURSOR loaded','$'
.OK     db      'CURSOR unloaded','$'
.NOK    db      'Failed to unload CURSOR','$'
; --------------------------------------
; IN (bx) OUT (eax)
ChangeIntVect:
        push    si
        mov     si,cs
        xchg    si,[bx+2]              ; -> SI is offset in IVT
        push    ds                      ; (1)
        xor     ax,ax
        mov     eax,[cs:bx]
        xchg    eax,[si]
        pop     ds                      ; (1)
        mov     [bx],eax
        pop     si
        ret
; --------------------------------------

使用方法

要安装驱动程序,只需运行裸机CURSOR.COM程序。
要卸载驱动程序,只需再次运行裸机CURSOR.COM程序。
安装后,您可以与驱动程序进行通信。
在应用程序中,您可以使用新的Video BIOS子功能:

  • AX=0F01h EBX="CURS" EnableGraphicsCursor
  • AX=0F02h EBX="CURS" DisableGraphicsCursor

在命令提示符下,使用命令尾部运行CURSOR.COM:

  • CURSOR ?报告光标当前处于启用状态还是禁用状态。
  • CURSOR 1现在启用光标。 (为DOSBox添加)
  • CURSOR 0现在禁用光标。 (为DOSBox添加)
  • CURSOR *显示帮助文本。 (*是任何文字)

为了最大程度地减少对非感知应用程序的影响,驱动程序在安装时默认禁用了光标。在图形模式下运行的有意识的应用程序需要显式启用光标。建议在任何输入过程附近都启用和禁用光标。请记住,驱动程序并非旨在提供无所不在的光标!

DOSBox很特殊

就像普通的MS-DOS(6.20)一样,DOSBox(0.74)在图形模式下不会显示任何光标。安装驱动程序将提供一个!
但是:

  • 因为与普通的DOS不同,DOSBox永远不会在输入过程中调用int28h中断,因此想要在命令提示符下闪烁光标的用户将必须手动启用光标。只需发出命令“ CURSOR 1”。
  • 尽管按下 Ins 键后,DOSBox会更新BIOS.KeyboardFlags位于线性地址0417h的第7位,但它只能在插入模式下运行。因此,游标驱动程序将更改游标的外观,但仍将是纯粹的外观更改。
  • DOSBox 0.74不支持单色屏幕15:640x350x2

不了解的应用程序

前段时间,我在CodeReview上发布了Rich Edit Form Input程序。这是一个有关输入的应用程序。尽管该程序未专门针对图形屏幕,但程序中没有阻止其在图形屏幕上运行的内容。只是缺少游标会很烦人。
好吧...不再安装今天的CURSOR driver。并且由于此应用程序中的所有输入都使用DOS输入功能,因此如果在真正的DOS上运行,光标将自动出现。如果在DOSBox上,则必须在命令提示符下手动启用光标。