Text Mode Cursor

来自osdev
跳到导航 跳到搜索

光标会自动移动到最后一个书写字符之后的一个位置。在文本模式中,光标的工作方式与高级语言中不同。 它只是一个可由操作系统调整大小、显示、隐藏和移动的闪烁区域。

通过BIOS

要使用BIOS操作光标,请使用int 0x10,屏幕功能的中断。

启用游标

启用光标功能还允许您设置开始和结束扫描线,即光标开始和结束的行。 最高处扫描线为0,最低处扫描线为最大扫描线(通常为15)。

  • AH = 0x01
  • CH = start scanline
  • CL = end scanline

禁用光标

  • AH = 0x01
  • CH = 0x3F (bits 0-7 unused, bit 5 disables cursor, bits 0-4 control cursor shape)

移动光标

  • AH = 0x02
  • BH = display page (usually, if not always 0)
  • DH = row
  • DL = column

获取光标数据

  • AH = 0x03
  • BH = display page (usually, if not always 0)

返回值:

  • CH = start scanline
  • CL = end scanline
  • DH = row
  • DL = column

不通过BIOS

在没有BIOS访问的情况下,操作光标需要将数据直接发送到硬件。

启用光标

启用游标功能还允许您设置开始和结束扫描线,即游标开始和结束的行。 最高处扫描线为0,最低处扫描线为最大扫描线(通常为15)。

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);
}

禁用光标

C语言源代码

void disable_cursor()
{
	outb(0x3D4, 0x0A);
	outb(0x3D5, 0x20);
}

汇编源代码

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

移动光标

请记住,不需要每次显示新字符时都更新光标的位置。 相反,只在打印完整个字符串之后才更新它会更快。

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));
}

汇编中的源代码

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

获取光标位置

使用此代码,您将获得:pos = y * VGA_WIDTH + x。 要获得坐标,只需计算: y = pos / VGA_WIDTH; x = pos % VGA_WIDTH;.

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;
}

基于字体的“图形”光标

用途

在DOS时代,完全不使用硬件VGA光标是很常见的,而是覆盖VGA字体来创建箭头指针,就像在图形模式中一样。 例如,在Norton UtilitiesNorton Diskedit的DOS版本中使用了该技术。

Arrow cursor in text mode

您可以通过光标颜色轻松地发现此字体更改光标: 由于只有字体被更改,属性字节未被触及,指针在移动时会改变颜色。

这里是论坛TUI的另一个例子,具有8x8个字符和指形光标

如何实现

基本原理是从屏幕上存储4个字节(2x2),将它们的VGA字体复制到一些未使用的方框图字符(0xC0-0xDF),或者将箭头屏蔽,然后在屏幕上写入这些2x2方框图字符。 然后,当鼠标移动时,屏幕上恢复原来的4个字节,整个过程在新位置重复。

使用框绘制字符很重要,因为通常VGA将字体显示为9x16,添加了一个空的第9列,这将导致光标中的 “间隙”。 对于方框图字符,第9列是第8列的副本,因此不会将指针一分为二。 如果您使用8x8字符(如80x50或132x50模式),则没有字符分隔栏,您可以随意使用任何字符。 在上面的论坛示例中,您可以在字符0xF0-0xF3处发现ASCII表上的光标。

尽管箭头大小与一个字符(通常为8x16或8x8)相同,但由于指针可以以像素精度移动,它可以在水平和垂直方向与下一个字符重叠,因此总共需要4个字节:

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

您可以使用BIOS读取VGA字体,或者如果您已经处于保护模式,则可以使用VGA寄存器读取VGA字体。 阅读 VGA字体 文章了解更多信息。

通过将鼠标光标除以字体大小来计算字符位置:cx=mx/8和cy=my/16。 然后计算my%16以获得要修改的字体字形的第一个字节,并计算mx%8以获得您必须按其移位箭头掩码的移位值。

关于GRUB的提醒

如果grub.cfg中的timeout设置为0,光标将被禁用,您需要自己启用它。 其它情况下,GRUB将为您启用光标。 由于这种不一致,因此始终启用光标是一个好主意。 即使没有将超时设置为0,将来也可能需要,或者有人可能会在他们的系统上更改timeout。

另见

外部链接