问题描述
在VGA图形模式下,光标不会显示,但BIOS会跟踪其位置。对于每个可用的显示页,BIOS都会在光标保存区域中记录光标的列和行坐标(不一定是X和Y坐标),从线性地址0450h开始16个字节。有趣的事实:BIOS也不必要地更新CRT控制器的光标位置高和光标位置低。
自从开始在图形屏幕上运行的应用程序以来,就不得不创建自己的游标,因此我完全意识到,我也将不得不提供自己的游标。
尽管有一个小故障。显然,DOS希望用户在图形屏幕上进行操作时,能够在不借助任何光标的情况下编辑命令行!从应用程序调用的DOS.BufferedInput function 0Ah也是如此。
那么如何在应用程序和命令提示符下将光标添加到图形视频模式呢?
(这是self-answer)
解决方法
要在用户应用程序和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通常报告以下内容:视频模式编号,列数和活动显示页面。这些项目在
AX
和BH
寄存器中返回。添加的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上,则必须在命令提示符下手动启用光标。