Text Mode Cursor: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
m (→‎Source in C: Cleaned up code)
(Rewrote most of the page)
Line 1: Line 1:
==Moving the Cursor with the BIOS==
==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:

To move the cursor with the [[BIOS]], use int 0x10 (the interrupt for screen functions). These are the registers used:

* AH = 0x02
* AH = 0x02
* BH = Display Page (This is usually, if not always, 0)
* BH = display page (usually, if not always 0)
* DH = The row
* DH = row
* DL = The column
* DL = column


===Without the BIOS===
Then, with a quick call to interrupt 0x10, you should have yourself a movable type cursor.


Without [[BIOS]] access, moving the cursor requires sending data directly to the hardware.
==Moving the Cursor without the BIOS==
Without access to [[BIOS]] calls and functions, moving the cursor requires using video hardware control. Lucky it is a simple procedure.


'''Source in C'''
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]].


===Source in C===
<source lang="c">
<source lang="c">
/* void update_cursor(int row, int col)
void update_cursor(int x, int y)
{
* by Dark Fiber
uint16_t pos = y * VGA_WIDTH + x;
*/

void update_cursor(int row, int col)
outb(0x3D4, 0x0F);
{
outb(0x3D5, (uint8_t) (pos & 0xFF));
unsigned short position=(row*80) + col;
outb(0x3D4, 0x0E);
outb(0x3D5, (uint8_t) ((pos >> 8) & 0xFF));
// 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>
</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)


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 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 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):

<source lang="asm">
<source lang="asm">
; Set cursor position (text mode 80x25)
; Set cursor position (text mode 80x25)
; @param BL The row on screen, starts from 0
; @param BL The row on screen, starts from 0
; @param BH The column on screen, starts from 0
; @param BH The column on screen, starts from 0
set_cursor:
;=============================================================================
set_cursor: pushfq
pushfq
push rax
push rax
push rbx
push rbx
Line 45: Line 44:
push rdx
push rdx


;unsigned short position = (row*80) + col;
; unsigned short position = (row*80) + col
;AX will contain 'position'
; AX will contain 'position'
mov ax,bx
mov ax, bx
and ax,0ffh ;set AX to 'row'
and ax, 0ffh ; set AX to 'row'
mov cl,80
mov cl, 80
mul cl ;row*80
mul cl ; row * 80
mov cx,bx
mov cx, bx
shr cx,8 ;set CX to 'col'
shr cx, 8 ; set CX to 'col'
add ax,cx ;+ col
add ax, cx ; + col
mov cx,ax ;store 'position' in CX
mov cx, ax ; store 'position' in CX
;cursor LOW port to vga INDEX register
; cursor LOW port to vga INDEX register
mov al,0fh
mov al, 0fh
mov dx,3d4h ;VGA port 3D4h
mov dx, 3d4h ; VGA port 3D4h
out dx,al
out dx, al
mov ax,cx ;restore 'postion' back to AX
mov ax, cx ; restore 'postion' back to AX
mov dx,3d5h ;VGA port 3D5h
mov dx, 3d5h ; VGA port 3D5h
out dx,al ;send to VGA hardware
out dx, al ; send to VGA hardware
;cursor HIGH port to vga INDEX register
; cursor HIGH port to vga INDEX register
mov al,0eh
mov al, 0eh
mov dx,3d4h ;VGA port 3D4h
mov dx, 3d4h ; VGA port 3D4h
out dx,al
out dx, al
mov ax,cx ;restore 'position' back to AX
mov ax, cx ; restore 'position' back to AX
shr ax,8 ;get high byte in 'position'
shr ax, 8 ; get high byte in 'position'
mov dx,3d5h ;VGA port 3D5h
mov dx, 3d5h ; VGA port 3D5h
out dx,al ;send to VGA hardware
out dx, al ; send to VGA hardware


pop rdx
pop rdx
Line 85: Line 84:


==Setting the Cursor Start and End Scanlines==
==Setting 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 (15 worked for me).


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===

'''Source in C'''

<source lang="c">
<source lang="c">
// enable the cursor and set the cursor start and end scanlines
void enable_cursor(uint8_t cursor_start, uint8_t cursor_end)
void enable_cursor(uint8_t cursor_start, uint8_t cursor_end)
{
{
Line 102: Line 102:
To make the cursor be a block instead of a line, try enable_cursor(0, 15).
To make the cursor be a block instead of a line, try enable_cursor(0, 15).


==Disabling The Cursor With the BIOS==
==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:


Disabling the cursor with the [[BIOS]] is done also through Int 0x10 with AH set to 0x01. These are the registers used:
* AH = 0x01
* AH = 0x01
* CH = 0x3F (bits 0-7 unused, bit 5 disables cursor, bits 0-4 control cursor shape)
* 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.)


===Without the BIOS===
And then Interrupt 0x10 and then vuala , there is no VISIBLE cursor on the screen


*Even on GUI modes the cursor remains active so don't think about it much.
Without [[BIOS]] access, disabling the cursor requires sending data directly to the hardware.

'''Source in C'''


==Disabling The 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">
<source lang="c">
/* void disable_cursor()
void disable_cursor()
{
* by Elad Ashkcenazi
outb(0x3D4, 0x0A);
* year 2017
outb(0x3D5, 0x20);
*/
}
void disable_cursor()
{
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 disabled, bits 0-4 control the cursor shape
}
</source>
</source>


===Source in assembly===
'''Source in Assembly'''


<source lang="asm">
<source lang="asm">
disable_cursor:
;written by Elad Ashkcenazi.
pushf
;year - 2017
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
disable_cursor: pushf
mov al, 0x3F ; bits 6-7 must be 0, if bit 5 set the cursor is disabled, bits 0-4 control the cursor shape
push rax
out dx, al
push rdx
pop rdx

pop rax
mov dx,0x3d4 ; one of VGA's Index registers controller
popf
mov al,0xa ; index 0xa is the LOW cursor shape register
ret
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
</source>
</source>


Line 158: Line 152:
* https://web.archive.org/web/20120324083032/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
* http://www.osdever.net/FreeVGA/vga/vga.htm
* http://www.osdever.net/FreeVGA/vga/vga.htm

[[Category:Video]]
[[Category:Video]]
[[Category:Text UI]]
[[Category:VGA]]
[[Category:VGA]]

Revision as of 01:34, 18 October 2017

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

See Also

External Links