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)
 
(46 intermediate revisions by 17 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 data area.
 
<source lang="c">
* CH = start scanline
/* void update_cursor(int row, int col)
* CL = end scanline
* by Dark Fiber
* DH = row
*/
* DL = column
void update_cursor(int row, int col)
 
{
==Without the [[BIOS]]==
unsigned short position=(row*80) + col;
 
Without BIOS access, manipulating the cursor requires sending data directly to the hardware.
// cursor LOW port to vga INDEX register
 
===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).
 
'''Source in C'''
 
<syntaxhighlight lang="c">
void enable_cursor(uint8_t cursor_start, uint8_t cursor_end)
{
outb(0x3D4, 0x0A);
outb(0x3D5, (inb(0x3D5) & 0xC0) | cursor_start);
 
outb(0x3D4, 0x0B);
outb(0x3D5, (inb(0x3D5) & 0xE0) | cursor_end);
}
</syntaxhighlight>
 
===Disabling the Cursor===
 
'''Source in C'''
 
<syntaxhighlight lang="c">
void disable_cursor()
{
outb(0x3D4, 0x0A);
outb(0x3D5, 0x20);
}
</syntaxhighlight>
 
'''Source in Assembly'''
 
<syntaxhighlight lang="asm">
disable_cursor:
pushf
push eax
push edx
 
mov dx, 0x3D4
mov al, 0xA ; low cursor shape register
out dx, al
 
inc dx
mov al, 0x20 ; bits 6-7 unused, bit 5 disables the cursor, bits 0-4 control the cursor shape
out dx, al
 
pop edx
pop eax
popf
ret
</syntaxhighlight>
 
===Moving the Cursor===
 
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(0x3D5, (unsigned char)(position&0xFF));
// cursor HIGH port to vga INDEX register
outb(0x3D4, 0x0E);
pos |= ((uint16_t)inb(0x3D5)) << 8;
outb(0x3D5, (unsigned char )((position>>8)&0xFF));
return pos;
}
}
</source>
</syntaxhighlight>
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 required is wisest)
 
==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==
* [[VGA Hardware]]
* [[Text UI]]
 
===External Links===
* http://www.bookcaseosdever.comnet/libraryFreeVGA/dosvga/ints/int10textcur.htmlhtm
* https://web.archive.org/web/20080731014051/http://www.bookcase.com:80/library/dos/ints/int10.html
*http://www.arl.wustl.edu/~lockwood/class/cs306/books/artofasm/Chapter_13/CH13-2.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
 
[[Category:Video]]
[[Category:Text UI]]
[[Category:VGA]]