Text Mode Cursor: Difference between revisions

m
Bot: Replace deprecated source tag with syntaxhighlight
[unchecked revision][unchecked revision]
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(20 intermediate revisions by 12 users not shown)
Line 1:
In [[text mode]], the cursor does not work the same way as in high-level languages, automatically moving to one place after the last written character. Instead, it is simply a blinking area that can be resized, shown, hidden, and moved by the OS.
==Moving the Cursor with the BIOS==
 
Moving the cursor with the [[BIOS]] is done through Int 0x10 (The general interrupt for screen functions) with AH set to 0x02. These are the registers used:
==With the [[BIOS]]==
 
To manipulate the cursor with the BIOS, use int 0x10, the interrupt for screen functions.
 
===Enabling the Cursor===
 
Enabling the cursor also allows you to set the start and end scanlines, the rows where the cursor starts and ends. The highest scanline is 0 and the lowest scanline is the maximum scanline (usually 15).
 
* AH = 0x01
* CH = start scanline
* CL = end scanline
 
===Disabling the Cursor===
 
* AH = 0x01
* CH = 0x3F (bits 6-7 unused, bit 5 disables cursor, bits 0-4 control cursor shape)
 
===Moving the Cursor===
 
* AH = 0x02
* BH = Displaydisplay Pagepage (This is usually, if not always, 0)
* DH = The row
* DL = The column
 
===Get Cursor Data===
Then, with a quick call to interrupt 0x10, you should have yourself a movable type cursor.
 
* AH = 0x03
==Moving the Cursor without the BIOS==
* BH = display page (usually, if not always 0)
Without access to [[BIOS]] calls and functions, moving the cursor requires using video hardware control. Lucky it is a simple procedure.
 
The return values:
Note, this quick example assumes 80x25 screen mode. Also note that the base port (here assumed to be 0x3D4) should be read from the [[BIOS]] [[Memory Map (x86)#BIOS Data Area .28BDA.29|data area]].
 
* CH = start scanline
===Source in C===
* CL = end scanline
<source lang="c">
* DH = row
/* void update_cursor(int row, int col)
* byDL Dark= Fibercolumn
*/
void update_cursor(int row, int col)
{
unsigned short position=(row*80) + col;
// cursor LOW port to vga INDEX register
outb(0x3D4, 0x0F);
outb(0x3D5, (unsigned char)(position&0xFF));
// cursor HIGH port to vga INDEX register
outb(0x3D4, 0x0E);
outb(0x3D5, (unsigned char )((position>>8)&0xFF));
}
</source>
Note that the 2 parameters 'row' & 'col' passed to the function above start from zero, not from 1. And keep in mind that in/out to [[VGA Hardware]] is a slow operation. So using the hardware registers to remember of the current character location (row, col) is bad practice -- and updating position after each displayed character is poor practice (updating it only when a line/string is complete is wiser and hiding it until a user prompt is wisest)
 
===SourceWithout inthe assembly=[[BIOS]]==
Since BIOS services can't be accessed in 64bit long mode, the following routine shows how to move cursor without BIOS in VGA text 80x25 (can be altered a bit to fit protected mode):
<source lang="asm">
; Set cursor position (text mode 80x25)
; @param BL The row on screen, starts from 0
; @param BH The column on screen, starts from 0
;=============================================================================
set_cursor: pushfq
push rax
push rbx
push rcx
push rdx
 
Without BIOS access, manipulating the cursor requires sending data directly to the hardware.
;unsigned short position = (row*80) + col;
;AX will contain 'position'
mov ax,bx
and ax,0ffh ;set AX to 'row'
mov cl,80
mul cl ;row*80
mov cx,bx
shr cx,8 ;set CX to 'col'
add ax,cx ;+ col
mov cx,ax ;store 'position' in CX
;cursor LOW port to vga INDEX register
mov al,0fh
mov dx,3d4h ;VGA port 3D4h
out dx,al
mov ax,cx ;restore 'postion' back to AX
mov dx,3d5h ;VGA port 3D5h
out dx,al ;send to VGA hardware
;cursor HIGH port to vga INDEX register
mov al,0eh
mov dx,3d4h ;VGA port 3D4h
out dx,al
mov ax,cx ;restore 'position' back to AX
shr ax,8 ;get high byte in 'position'
mov dx,3d5h ;VGA port 3D5h
out dx,al ;send to VGA hardware
 
===Enabling the Cursor===
pop rdx
pop rcx
pop rbx
pop rax
popfq
ret
</source>
 
Enabling the cursor also allows you to set the start and end scanlines, the rows where the cursor starts and ends. The highest scanline is 0 and the lowest scanline is the maximum scanline (usually 15).
==Disabling The Cursor With the BIOS==
 
'''Source in C'''
Disabling the cursor with the [[BIOS]] is done also through Int 0x10 with AH set to 0x01. These are the registers used:
* AH = 0x01
* CH = 0x3f ; bits 6-7 are unused , if bit 5 set the cursor is disable (this bit may be cleared in the interrupt caller , see "Disabling The Cursor Without the BIOS" for safer way to disable the cursor) , bits 0-4 controll the cursor shape (if bits 0-4 all set the cursor is unvisiable regradless bit 5's state.)
 
<syntaxhighlight lang="c">
And then Interrupt 0x10 and then vuala , there is no VISIBLE cursor on the screen
void enable_cursor(uint8_t cursor_start, uint8_t cursor_end)
{
outb(0x3D4, 0x0A);
outb(0x3D5, (inb(0x3D5) & 0xC0) | cursor_start);
 
outb(0x3D4, 0x0B);
*Even on GUI modes the cursor remains active so don't think about it much.
outb(0x3D5, (inb(0x3D5) & 0xE0) | cursor_end);
}
</syntaxhighlight>
 
===Disabling Thethe Cursor Without the BIOS===
To diable the cursor without the bios we need to use vga's 0x3d4 index port and his data register located one port above it, we need to set bits 5-0 of register index 0x0a , the LOW cursor shape (we don't need to alter the HIGH cursor shape, he doesn't contain the 'cursor disable' bit inside him .), here's how it translate into code:
===Source in C===
<source lang="c">
/* void disable_cursor()
* by Elad Ashkcenazi
* year 2017
*/
void disable_cursor()
{
 
'''Source in C'''
outb(0x3D4, 0x0A); // LOW cursor shape port to vga INDEX register
outb(0x3D5, 0x3f); //bits 6-7 must be 0 , if bit 5 set the cursor is disable , bits 0-4 controll the cursor shape .
}
</source>
 
<syntaxhighlight lang="c">
===Source in assembly===
void disable_cursor()
{
outb(0x3D4, 0x0A);
outb(0x3D5, 0x20);
}
</syntaxhighlight>
 
'''Source in Assembly'''
<source lang="asm">
;written by Elad Ashkcenazi.
;year - 2017
 
<syntaxhighlight lang="asm">
disable_cursor:
pushf
push eax
push edx
 
mov dx, 0x3D4
disable_cursor: pushf
mov al, 0xA ; low cursor shape register
push rax
out dx, al
push rdx
 
inc dx
mov dx,0x3d4 ;one of VGA's Index registers controller
mov al, 0x20 ; bits 6-7 unused, bit 5 disables the mov alcursor,0xa bits 0-4 ; index 0xa iscontrol the LOW cursor shape register
out dx, al
 
pop edx
inc dx ;one of VGA's data register, the data register of port 0x3d4, allows reads and writes to VGA's registers.
pop eax
mov al,0x3f ; bits 6-7 must be 0 , if bit 5 set the cursor is disable , bits 0-4 controll the cursor shape
popf
out dx,al
ret
pop rdx
</syntaxhighlight>
pop rax
 
popf
===Moving the Cursor===
ret
 
</source>
Keep in mind that you don't need to update the cursor's location every time a new character is displayed. It would be faster to instead only update it after printing an entire string.
 
'''Source in C'''
 
<syntaxhighlight lang="c">
void update_cursor(int x, int y)
{
uint16_t pos = y * VGA_WIDTH + x;
 
outb(0x3D4, 0x0F);
outb(0x3D5, (uint8_t) (pos & 0xFF));
outb(0x3D4, 0x0E);
outb(0x3D5, (uint8_t) ((pos >> 8) & 0xFF));
}
</syntaxhighlight>
 
'''Source in Assembly'''
 
<syntaxhighlight lang="asm">
Cursor:
VGA.Width equ 80
 
.SetCoords:
; input bx = x, ax = y
; modifies ax, bx, dx
 
mov dl, VGA.Width
mul dl
add bx, ax
 
.SetOffset:
; input bx = cursor offset
; modifies al, dx
 
mov dx, 0x03D4
mov al, 0x0F
out dx, al
 
inc dl
mov al, bl
out dx, al
 
dec dl
mov al, 0x0E
out dx, al
 
inc dl
mov al, bh
out dx, al
ret
</syntaxhighlight>
 
===Get Cursor Position===
 
With this code, you get: <tt>pos = y * VGA_WIDTH + x</tt>. To obtain the coordinates, just calculate: <tt>y = pos / VGA_WIDTH; x = pos % VGA_WIDTH;</tt>.
 
'''Source in C'''
 
<syntaxhighlight lang="c">
uint16_t get_cursor_position(void)
{
uint16_t pos = 0;
outb(0x3D4, 0x0F);
pos |= inb(0x3D5);
outb(0x3D4, 0x0E);
pos |= ((uint16_t)inb(0x3D5)) << 8;
return pos;
}
</syntaxhighlight>
 
==Font based "graphical" cursor==
 
===Used by===
 
Back in the DOS days it was quite common not to use the hardware VGA cursor at all, instead overriding the VGA fonts to create an arrow pointer just like in graphical modes. This technique was used in [https://en.wikipedia.org/wiki/Norton_Utilities Norton Utilities] or the DOS version of [[Norton Diskedit]] for example.
 
[[Image:Itmouse.png|Arrow cursor in text mode]]
 
You can easily spot this font altering cursor by the cursor color: as only the fonts are altered and the attribute bytes are untouched, the pointer changes color as you move it around.
 
Here's another example from the Screenshots forum [https://forum.osdev.org/download/file.php?id=2264&mode=view TUI with 8x8 characters and pointing finger-shaped cursor]
 
===How to implement===
 
The basic principle is to store 4 bytes (2x2) from the screen, copy their [[VGA Fonts]] to some unused box drawing characters (0xC0 - 0xDF), OR mask the arrow onto then write those 2x2 box drawing characters on screen. Then when the mouse moved, the original 4 bytes are restored on screen, and the whole procedure repeated on the new position.
 
It is important to use box drawing characters, because normally VGA displays fonts as 9x16, adding an empty 9th column, which would cause a "gap" in the pointer. With the box drawing characters that 9th column is a copy of the 8th column, and therefore does not cut the pointer in half. If you use 8x8 characters (like 80x50 or 132x50 modes) then there are no character separator columns, and you are free to use any character you like. On the forum example above you can spot the cursor on the ASCII table at characters 0xF0 - 0xF3.
 
Although the arrow size is the same as one character (typically 8x16 or 8x8), as the pointer can be moved with pixel precision it can overlap with the next character both horizontally and vertically, thus giving the total 4 bytes requirement:
<pre>
char 1 attr 1 char 2 attr 2
........|????????|........|???????? first line
........|????????|........|???????? (note attribute bytes are untouched)
........|????????|........|????????
....x...|????????|........|????????
....xx..|????????|........|????????
....xxx.|????????|........|????????
....xxxx|????????|........|????????
....xxxx|????????|x.......|????????
--------+--------+--------+--------
....xxxx|????????|xx......|???????? second line
....xxxx|????????|xxx.....|????????
....xxxx|????????|xxxx....|????????
.......x|????????|x.......|????????
.......x|????????|x.......|????????
........|????????|xx......|????????
........|????????|........|????????
........|????????|........|????????
char 3 attr 3 char 4 attr 4
</pre>
You can read the VGA Fonts with BIOS, or if you're already in protected mode, with VGA registers. Read [[VGA Fonts]] article for more information.
 
The character positions are calculated by dividing mouse coordinates by font size: cx = mx / 8 and cy = my / 16. Then you calculate my % 16 to get the first byte of the font glyph to be modified, and mx % 8 to get the shift value by which you have to shift the arrow mask.
 
==A Note on GRUB==
 
If the timeout is set to 0 in your grub.cfg, the cursor will be disabled and you will need to enable it yourself. Otherwise, [[GRUB]] will enable the cursor for you. Because of this inconsistency, it is a good idea to always enable the cursor. Even if you don't set the timeout to 0, you might want to in the future, or someone might change it on their system.
 
==See Also==
* [http://wiki.osdev.org/VGA_Hardware [VGA Hardware]]
* [[Text UI]]
 
===External Links===
* http://www.bookcaseosdever.comnet/libraryFreeVGA/dosvga/ints/int10textcur.html (dead link)htm
* https://web.archive.org/web/20080731014051/http://www.bookcase.com:80/library/dos/ints/int10.html
* https://web.archive.org/web/20120324083032/http://www.arl.wustl.edu/~lockwood/class/cs306/books/artofasm/Chapter_13/CH13-2.html
* https://en.wikipedia.org/wiki/VGA-compatible_text_mode
* http://www.osdever.net/FreeVGA/vga/vga.htm
 
[[Category:Video]]
[[Category:Text UI]]
[[Category:VGA]]