Text Mode Cursor
Moving the Cursor
With the BIOS
To move the cursor with the BIOS, use int 0x10 (the interrupt for screen functions). These are the registers used:
- AH = 0x02
- BH = display page (usually, if not always 0)
- DH = row
- DL = column
Without the BIOS
Without BIOS access, moving the cursor requires sending data directly to the hardware.
Source in 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));
}
Note that the x and y positions start from 0 in the top-left corner. Keep in mind you don't need to update this every time a new character is displayed. It would be faster to instead only update it after printing an entire string or until a user prompt.
Source in Assembly
Since BIOS services cannot be accessed in 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):
; 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
; 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
pop rdx
pop rcx
pop rbx
pop rax
popfq
ret
Setting the Cursor Start and End Scanlines
To set the cursor start and end scanlines, use the Cursor Start Register (0x0A) and the Cursor End Register (0x0B). The highest scanline is 0 and the lowest scanline is the maximum scanline (usually 15).
Source in 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(0x3E0) & 0xE0) | cursor_end);
}
To make the cursor be a block instead of a line, try enable_cursor(0, 15).
Disabling The Cursor
With the BIOS
To move the cursor with the BIOS, use int 0x10 (the interrupt for screen functions). These are the registers used:
- AH = 0x01
- CH = 0x3F (bits 0-7 unused, bit 5 disables cursor, bits 0-4 control cursor shape)
Without the BIOS
Without BIOS access, disabling the cursor requires sending data directly to the hardware.
Source in C
void disable_cursor()
{
outb(0x3D4, 0x0A);
outb(0x3D5, 0x20);
}
Source in Assembly
disable_cursor:
pushf
push rax
push rdx
mov dx, 0x3D4 ; one of VGA's Index registers controller
mov al, 0xa ; index 0xa is the LOW cursor shape register
out dx, al
inc dx ; one of VGA's data registers, port 0x3D4, allows reads and writes to VGA's registers
mov al, 0x3F ; bits 6-7 must be 0, if bit 5 set the cursor is disabled, bits 0-4 control the cursor shape
out dx, al
pop rdx
pop rax
popf
ret