相比前几篇的内容,本篇不仅内容更为简单,而且与显示相关,更为有趣。首先通过调用VBE的显示模式提高显示画面的分辨率,然后分别实现按下键盘按键显示对应的字符,以及通过鼠标移动窗口。因为是以前面讲过的很多内容为基础,程序代码很简单,而且能切实看到成果,也更有趣。
1. 提高画面分辨率
现在要把显示画面的分辨率提高到640x480,就又要修改BIOS的画面模式设定所用的汇编语言代码了。
; 设定画面模式
MOV BX,0x4101 ; VBE的640x480x8bit彩色
MOV AX,0x4f02
INT 0x10
MOV BYTE [VMODE],8 ; 记录下画面模式
MOV WORD [SCRNX],640
MOV WORD [SCRNY],480
MOV DWORD [VRAM],0xe0000000
记得之前切换画面模式用的汇编语言指令是“AH = 0, AL = 画面模式号码”,但是上面这段代码却有所不同,用的是AX = 0x4f02。这是什么原因呢?
从前电脑的规格是以IBM公司为中心决定的,IBM规定了画面显示模式的规格,各家显卡公司也按照这一规格来进行实现。而后来各个显卡公司开发出了更多性能更好的显卡,具有各种画面显示模式,设置和使用的方法也各不相同,导致程序员比较无所适从。于是多家显卡公司进行协商,成立了VESA协会。这个协会制定了几乎可以通用的设置方法,制作了专门的BIOS,这个追加的BIOS被称为VESA BIOS extension,简称为VBE。利用它就可以扩展显卡的显示模式了。
VBE的画面模式号码如下:
- 0x101: 640x480x8bit 彩色
- 0x103: 800x600x8bit 彩色
- 0x105: 1024x768x8bit 彩色
- 0x107: 1280x1024x8bit 彩色
为了在真机上使用VBE的显示模式,我们还需要进行一些确认。
(1) 确认是否支持VBE?
有些公司的产品仍然不支持VBE,因此无法使用VBE的显示模式。我们通过如下操作来进行确认:
; 确认VBE是否存在
MOV AX,0x9000
MOV ES,AX
MOV DI,0
MOV AX,0x4f00
INT 0x10
CMP AX,0x004f
JNE scrn320
这里给ES赋值0x9000,给DI赋值为0,给AX赋值为0x4f00,再执行INT 0x10,如果有VBE的话,AX就会变成0x004f。否则说明没有VBE,仍然只能使用之前320x200的画面。而为ES和DI进行赋值,是因为此显卡能利用的VBE信息将要写入内存中以ES:DI开始的512字节中。
(2) 确认VBE的版本是否在2.0以上
使用高分辨率,也需要VBE的版本在2.0以上。
; VBE版本确认
MOV AX,[ES:DI+4]
CMP AX,0x0200
JB scrn320 ; if (AX < 0x0200) goto scrn320
(3) 即使VBE版本在2.0以上,也还是需要确认画面模式0x105是否能够使用:
; 获取画面模式信息
MOV CX,VBEMODE
MOV AX,0x4f01
INT 0x10
CMP AX,0x004f
JNE scrn320
这里对AX的值进行了确认,如果是0x004f以外的值,所指定的画面模式就不能使用。
此次取得的画面模式信息也被写入内存从ES:DI开始的256字节中,这样刚才VBE的版本信息会被覆盖。但确认VBE版本之后这个信息就不需要了,因此没什么影响。
(4) 其他画面模式信息确认
这还没完,还有最后的几项信息需要确认:
- 颜色数是否为8
- 是否为调色板模式
- 画面模式号码是否可以加上0x4000再进行指定
; 画面模式信息确认
CMP BYTE [ES:DI+0x19],8
JNE scrn320
CMP BYTE [ES:DI+0x1b],4
JNE scrn320
MOV AX,[ES:DI+0x00]
AND AX,0x0080
JZ scrn320 ; 模式属性的bit7是0,放弃
以上这些信息如果都确认OK,那么就可以使用VBE的画面模式了。如果确认不通过,则还是只能使用之前的分辨率。
; 画面模式切换
MOV BX,VBEMODE+0x4000
MOV AX,0x4f02
INT 0x10
MOV BYTE [VMODE],8 ;
MOV AX,[ES:DI+0x12]
MOV [SCRNX],AX
MOV AX,[ES:DI+0x14]
MOV [SCRNY],AX
MOV EAX,[ES:DI+0x28]
MOV [VRAM],EAX
JMP keystatus
scrn320:
MOV AL,0x13 ; VGA图,320x200x8bit彩色
MOV AH,0x00
INT 0x10
MOV BYTE [VMODE],8 ;
MOV WORD [SCRNX],320
MOV WORD [SCRNY],200
MOV DWORD [VRAM],0x000a0000
调整分辨率后,画面明显变大了。
2. 键盘输入与鼠标移动窗口
前面已经讲过关于键盘和鼠标的内容了。但是键盘只是实现了按下按键显示按键码,而鼠标也只是实现了移动鼠标箭头,其实还没有什么实用的功能。本篇中来实现按下键盘按键显示对应的字符,以及通过鼠标移动窗口的功能。
2.1 键盘信息输入
前面已经讲过,按下一个键盘按键,会显示一个按键码,松开的时候也会显示一个按键码。利用这一点,我们来实现按键显示对应字符的功能。
比如按下按键A时,显示的按键码是1E,按下B的时候,显示的按键码是30,……,关于每个按键的按键码形成了一张对照表。根据表中的按键码数值,可以编写keytable对照表,根据按键码来显示按键的字符。
static char keytable[0x54] = {
0, 0, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '^', 0, 0,
'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '@', '[', 0, 0, 'A', 'S',
'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', ':', 0, 0, ']', 'Z', 'X', 'C', 'V',
'B', 'N', 'M', ',', '.', '/', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, '7', '8', '9', '-', '4', '5', '6', '+', '1',
'2', '3', '0', '.'
};
if (256 <= i && i <= 511) { /* 键盘数据 */
sprintf(s, "%02X", i - 256);
putfonts8_asc_sht(sht_back, 0, 16, COL8_FFFFFF, COL8_008484, s, 2);
if (i < 256 + 0x54) {
if (keytable[i - 256] != 0) {
s[0] = keytable[i - 256];
s[1] = 0;
putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, s, 1);
}
}
}
目前的程序还只能显示一个字符,再对程序进行修改,使其可以显示多个字符,并且修改了显示画面。
int mx, my, i, cursor_x, cursor_c;
make_textbox8(sht_win, 8, 28, 144, 16, COL8_FFFFFF);
cursor_x = 8;
cursor_c = COL8_FFFFFF;
……
for (;;) {
io_cli();
if (fifo32_status(&fifo) == 0) {
io_stihlt();
} else {
i = fifo32_get(&fifo);
io_sti();
if (256 <= i && i <= 511) { /* 键盘数据 */
sprintf(s, "%02X", i - 256);
putfonts8_asc_sht(sht_back, 0, 16, COL8_FFFFFF, COL8_008484, s, 2);
if (i < 0x54 + 256) {
if (keytable[i - 256] != 0 && cursor_x < 144) { /* 一般字符 */
/* 显示一个字符就前移一次光标 */
s[0] = keytable[i - 256];
s[1] = 0;
putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, s, 1);
cursor_x += 8;
}
}
if (i == 256 + 0x0e && cursor_x > 8) { /* backspace */
/* 用空格键把光标消去后,后移一次光标 */
putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, " ", 1);
cursor_x -= 8;
}
/* 光标再显示 */
boxfill8(sht_win->buf, sht_win->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43);
sheet_refresh(sht_win, cursor_x, 28, cursor_x + 8, 44);
} else if (512 <= i && i <= 767) {
if (mouse_decode(&mdec, i - 512) != 0) {
sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y);
if ((mdec.btn & 0x01) != 0) {
s[1] = 'L';
}
if ((mdec.btn & 0x02) != 0) {
s[3] = 'R';
}
if ((mdec.btn & 0x04) != 0) {
s[2] = 'C';
}
putfonts8_asc_sht(sht_back, 32, 16, COL8_FFFFFF, COL8_008484, s, 15);
mx += mdec.x;
my += mdec.y;
if (mx < 0) {
mx = 0;
}
if (my < 0) {
my = 0;
}
if (mx > binfo->scrnx - 1) {
mx = binfo->scrnx - 1;
}
if (my > binfo->scrny - 1) {
my = binfo->scrny - 1;
}
sprintf(s, "(%3d, %3d)", mx, my);
putfonts8_asc_sht(sht_back, 0, 0, COL8_FFFFFF, COL8_008484, s, 10);
sheet_slide(sht_mouse, mx, my);
}
} else if (i == 10) {
putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484, "10[sec]", 7);
} else if (i == 3) {
putfonts8_asc_sht(sht_back, 0, 80, COL8_FFFFFF, COL8_008484, "3[sec]", 6);
} else if (i <= 1) {
if (i != 0) {
timer_init(timer3, &fifo, 0);
cursor_c = COL8_000000;
} else {
timer_init(timer3, &fifo, 1);
cursor_c = COL8_FFFFFF;
}
timer_settime(timer3, 50);
boxfill8(sht_win->buf, sht_win->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43);
sheet_refresh(sht_win, cursor_x, 28, cursor_x + 8, 44);
}
}
}
cursor_x用来记住光标显示的位置,输入一个字符后,变量就递增8,cursor_c则表示光标的颜色,并且每0.5s变化一次,产生闪烁效果。显示效果如下:
不过我这里遇到一个问题,按下一个按键松开之后,输入框中会一直重复输入这个字符,把输入框占满,而此时再按backspace键也没有反应。暂时还没找到原因,先继续往下学,看看后续能否解决这个问题
键盘显示字符做完了,鼠标也修改一下,程序也很简单,只需要在主程序鼠标处理的部分增加几行代码:
if ((mdec.btn & 0x01) != 0) {
/* 按下左键之后,就移动sht_win窗口 */
sheet_slide(sht_win, mx - 80, my - 8);
}
这样只要点击鼠标,窗口就会移动到鼠标点击的位置。
这一篇的内容可以说是非常简单了,而且实现效果立竿见影。不过从下一篇开始就要进入多任务的学习了,这部分内容是个难点,还是提前做好准备吧。