第14天 高分辨率及键盘输入
14.1 提高分辨率(1)
之前的分辨率为320*200*8彩色,VGA显卡。如果要使用新画面模式,就需要使用VBE画面模式。
切换到不使用 VBE 的画面模式时用 “AH = 0 ; AL= 画面模式号码; ”,而切换 到使用 VBE 的画面模式时用 “AX = 0x4f02 ; BX = 画面模式号码; ”。而这种必须使 用 VBE 才能利用的画面模式就称作 “ 新 ” 画面模式。
VBE 的画面模式号码如下:
0x101……640× 480× 8bit 彩色
0x103……800× 600× 8bit 彩色
0x105……1024× 768× 8bit 彩色
0x107……1280× 1024× 8bit 彩色
;画面设定 文件asmhead.nas
;修改前代码
; MOV AL,0x13 ;VGA显卡,320*200*8彩色
; MOV AH,0x00
; INT 0x10
; MOV BYTE [VMODE],8 ;记录画面模式
; MOV WORD [SCRNX],320;两个字节
; MOV WORD [SCRNY],200;两个字节
; MOV DWORD [VRAM],0x000a0000;VRAM是用来显示画面的内存,地址0xa0000~0xaffff的64KB
;修改后代码
MOV BX,0x4101 ;VBE显卡,640x480*8彩色
MOV AX,0x4f02
INT 0x10
MOV BYTE [VMODE],8 ;记录画面模式
MOV WORD [SCRNX],640;两个字节
MOV WORD [SCRNY],480;两个字节
MOV DWORD [VRAM],0xe0000000;VRAM是用来显示画面的内存,地址0xa0000~0xaffff的64KB
14.2 提高分辨率(2)
1° 首先确认VBE是否存在: 有 VBE 的话, AX 就会变为0x004f;而且此显卡能利用的VBE信息要写入到内存中以ES:DI开始的512字节中。
2° 检查VBE版本: if (AX < 0x0200) goto scrn320
3° 取得画面模式信息:通过 VBE 来查看一下画面模式 0x105 能不能使用。如果AX是0x004f以外的值,就意味着所指定的画面模式不能使用。
4° 画面模式信息中,重要的信息有如下 6 个:
WORD [ES : DI+0x00] : 模式属性 …… bit7 不是 1 就不好办 ( 能加上 0x4000 )
WORD [ES : DI+0x12] : X 的分辨率
WORD [ES : DI+0x14] : Y 的分辨率
BYTE [ES : DI+0x19] : 颜色数 …… 必须为 8
BYTE [ES : DI+0x1b] : 颜色的指定方法 …… 必须为 4 ( 4 是调色板模式 )
DWORD [ES : DI+0x28] : VRAM 的地址
5° 画面模式信息的确认:1)颜色数是否为8;2)是否为调色板模式;3)画面模式号码可否加上0x4000再进行指定。
6° 画面模式的切换
VBEMODE EQU 0x105 ;1024 x 768 x 8bit
; 画面模式号码如下
; 0x100 : 640 x 400 x 8bit
; 0x101 : 640 x 480 x 8bit
; 0x103 : 800 x 600 x 8bit
; 0x105 : 1024 x 768 x 8bit
; 0x107 : 1280 x 1024 x 8bit
BOTPAK EQU 0x00280000
DSKCAC EQU 0x00100000
DSKCAC0 EQU 0x00008000
;有关BOOT_INFO
CYLS EQU 0x0ff0 ;设定启动区
LEDS EQU 0x0ff1
VMODE EQU 0x0ff2 ;关于色彩数目的信息,颜色位数
SCRNX EQU 0x0ff4 ;分辨率的x
SCRNY EQU 0x0ff6 ;分辨率的y
VRAM EQU 0x0ff8 ;图像缓冲区的开始地址
ORG 0xc200 ;程序要装载到的位置,书本P79页已说明0xc200的由来
; 确认VBE是否存在
MOV AX,0x9000
MOV ES,AX
MOV DI,0
MOV AX,0x4f00
INT 0x10
CMP AX,0x004f
JNE scrn320
; 检查VBE的版本
MOV AX,[ES:DI+4]
CMP AX,0x0200
JB scrn320 ; if (AX < 0x0200) goto scrn320
; 取得画面模式信息
MOV CX,VBEMODE
MOV AX,0x4f01
INT 0x10
CMP AX,0x004f
JNE scrn320
; 画面模式信息的确认
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,所以放弃
; 画面模式的切换
MOV BX,VBEMODE+0x4000
MOV AX,0x4f02
INT 0x10
MOV BYTE [VMODE],8 ; 记下画面模式(参考C语言)
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显卡,320*200*8彩色
MOV AH,0x00
INT 0x10
MOV BYTE [VMODE],8 ;记录画面模式
MOV WORD [SCRNX],320;两个字节
MOV WORD [SCRNY],200;两个字节
MOV DWORD [VRAM],0x000a0000;VRAM是用来显示画面的内存,地址0xa0000~0xaffff的64KB
;用BIOS取得键盘上各种LED指示灯的状态
keystatus:
MOV AH,0x02
INT 0x16 ;keyboard BIOS
MOV [LEDS],AL
14.3 键盘输入(1)
在窗口中打印通过键盘输入的字符‘A’。
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 == 0x1e + 256) {
putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, "A", 1);
}
} else if (512 <= i && i <= 767) { /* 鼠标数据 */
(中略)
} else if (i == 10) { /* 10秒定时器 */
putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484, "10[sec]", 7);
} else if (i == 3) { /* 3秒定时器 */
putfonts8_asc_sht(sht_back, 0, 80, COL8_FFFFFF, COL8_008484, "3[sec]", 6);
} else if (i == 1) { /* 光标用定时器 */
(中略)
} else if (i == 0) { /* 光标用定时器 */
(中略)
}
}
}
14.4 键盘输入(2)
创建字符串数组,如果通过键盘按下对应字符,则在窗口中显示。
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;// 回车符,理解成'\n',表示字符串终止符
putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, s, 1);
}
}
} else if (512 <= i && i <= 767) { /* 鼠标数据 */
14.5 追记内容(1)
通过14.4节中已经实现在窗口中显示一个键盘输入的字符,下面将在窗口中实现连续输入字符。
有以下要求:1.将输入的位置存在光标,2.实时输入,3.可以删除字符。
思路:首先在窗口中画一个白底的输入框,再判断键盘中断产生时按下的字符是否在字符串keytable中。如果在,进一步确认是否为退格符,如果是则用空字符进行覆盖,否则显示该字符。光标用计时器也需要更新,调用boxfill8和sheet_refresh函数进行刷新光标。
// 绘制白底文本输入框
void make_textbox8(struct SHEET *sht, int x0, int y0, int sx, int sy, int c){
int x1 = x0 + sx, y1 = y0 + sy;
boxfill8(sht->buf, sht->bxsize, COL8_848484, x0 - 2, y0 - 3, x1 + 1, y0 - 3);
boxfill8(sht->buf, sht->bxsize, COL8_848484, x0 - 3, y0 - 3, x0 - 3, y1 + 1);
boxfill8(sht->buf, sht->bxsize, COL8_FFFFFF, x0 - 3, y1 + 2, x1 + 1, y1 + 2);
boxfill8(sht->buf, sht->bxsize, COL8_FFFFFF, x1 + 2, y0 - 3, x1 + 2, y1 + 2);
boxfill8(sht->buf, sht->bxsize, COL8_000000, x0 - 1, y0 - 2, x1 + 0, y0 - 2);
boxfill8(sht->buf, sht->bxsize, COL8_000000, x0 - 2, y0 - 2, x0 - 2, y1 + 0);
boxfill8(sht->buf, sht->bxsize, COL8_C6C6C6, x0 - 2, y1 + 1, x1 + 0, y1 + 1);
boxfill8(sht->buf, sht->bxsize, COL8_C6C6C6, x1 + 1, y0 - 2, x1 + 1, y1 + 1);
boxfill8(sht->buf, sht->bxsize, c, x0 - 1, y0 - 1, x1 + 0, y1 + 0);
return;
}
void HariMain(void)
{
(中略)
int cursor_x, cursor_c;
(中略)
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', '.'
};
(中略)
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 == 0x0e + 256 && cursor_x > 8){/* 按下删除符 */
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);
} else if (512 <= i && i <= 767) {/* 鼠标数据 */
(中略)
}
} else if (i == 10) {/* 10s计时器 */
(中略)
} else if (i == 3) {/* 3s计时器 */
(中略)
} else if (i <= 1) {/* 光标用计时器 */
if( i == 1){
timer_init(timer3, &fifo, 0); /* 设定为0 */
cursor_c = COL8_000000;
}else{
timer_init(timer3, &fifo, 1); /* 设定为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);
}
}
}
}
14.6 追记内容(2)
实现鼠标拖动窗口移动的功能,思路为当按下鼠标左键时,调用sheet_slide函数将sht_win的位置修改为(mx-80, my-8) 。
for (;;) {
io_cli();
if (fifo32_status(&fifo) == 0) {
io_stihlt();
} else {
i = fifo32_get(&fifo);
io_sti();
if (256 <= i && i <= 511) { /* 键盘数据 */
(中略)
} else if (512 <= i && i <= 767) { /* 鼠标数据 * */
if (mouse_decode(&mdec, i - 512) != 0) {
/* 收集了3字节的数据,所以显示出来 */
(中略)
/* 光标移动 */
(中略)
sheet_slide(sht_mouse, mx, my);
/* 从这里开始! */
if ((mdec.btn & 0x01) != 0) {
/* 按下左键、移动sht_win */
sheet_slide(sht_win, mx - 80, my - 8);
/* 到这里结束! */ }
}
} else if (i == 10) { /* 10秒定时器 */
putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484, "10[sec]", 7);
} else if (i == 3) { /* 3秒定时器 */
putfonts8_asc_sht(sht_back, 0, 80, COL8_FFFFFF, COL8_008484, "3[sec]", 6);
} else if (i <= 1) { /* 光标用定时器 */
(中略)
}
}
}