本篇内容开始进入一个新的主题——命令行,这是一个操作系统很基本的功能。本篇中首先实现命令行窗口的显示,做到能切换到窗口以及实现向窗口输入内容。接下来在之前键盘输入的基础上,增加对符号以及大小写字母的输入。最后再加入对其他锁定键的支持。
1. 创建命令行窗口与窗口输入
首先创建一个命令行窗口。有了之前图层相关内容的基础,创建命令行窗口其实也只是在显示画面上描绘出图案。这里我们同时显示任务A与命令行窗口:
1.1 创建命令行窗口
……
//代码段1
/* sht_back */
sht_back = sheet_alloc(shtctl);
buf_back = (unsigned char *) memman_alloc_4k(memman, binfo->scrnx * binfo->scrny);
sheet_setbuf(sht_back, buf_back, binfo->scrnx, binfo->scrny, -1);
init_screen8(buf_back, binfo->scrnx, binfo->scrny);
/* sht_cons */
sht_cons = sheet_alloc(shtctl);
buf_cons = (unsigned char *) memman_alloc_4k(memman, 256 * 165);
sheet_setbuf(sht_cons, buf_cons, 256, 165, -1);
make_window8(buf_cons, 256, 165, "console", 0);
make_textbox8(sht_cons, 8, 28, 240, 128, COL8_000000);
task_cons = task_alloc();
task_cons->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8;
task_cons->tss.eip = (int) &console_task;
task_cons->tss.es = 1 * 8;
task_cons->tss.cs = 2 * 8;
task_cons->tss.ss = 1 * 8;
task_cons->tss.ds = 1 * 8;
task_cons->tss.fs = 1 * 8;
task_cons->tss.gs = 1 * 8;
*((int *) (task_cons->tss.esp + 4)) = (int) sht_cons;
task_run(task_cons, 2, 2); /* level=2, priority=2 */
……
//代码段2
sheet_slide(sht_back, 0, 0);
sheet_slide(sht_cons, 32, 4);
sheet_slide(sht_win, 64, 56);
sheet_slide(sht_mouse, mx, my);
sheet_updown(sht_back, 0);
sheet_updown(sht_cons, 1);
sheet_updown(sht_win, 2);
sheet_updown(sht_mouse, 3);
……
void console_task(struct SHEET *sheet)
{
struct FIFO32 fifo;
struct TIMER *timer;
struct TASK *task = task_now();
int i, fifobuf[128], cursor_x = 8, cursor_c = COL8_000000;
fifo32_init(&fifo, 128, fifobuf, task);
timer = timer_alloc();
timer_init(timer, &fifo, 1);
timer_settime(timer, 50);
for (;;) {
io_cli();
if (fifo32_status(&fifo) == 0) {
task_sleep(task);
io_sti();
} else {
i = fifo32_get(&fifo);
io_sti();
if (i <= 1) {
if (i != 0) {
timer_init(timer, &fifo, 0);
cursor_c = COL8_FFFFFF;
} else {
timer_init(timer, &fifo, 1);
cursor_c = COL8_000000;
}
timer_settime(timer, 50);
boxfill8(sheet->buf, sheet->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43);
sheet_refresh(sheet, cursor_x, 28, cursor_x + 8, 44);
}
}
}
}
代码段1描绘了背景画面与命令行窗口等图层,并初始化命令行窗口任务;代码段2则设置好这些图层的显示位置与上下层关系。命令行窗口的任务consle_task中处理也比较简单,先是实现了光标的闪烁。运行效果如下:
1.2 向命令行窗口输入
当前状态下还是没有实现对命令行窗口输入,而且命令行窗口的标题栏显示还是灰色的,我们一点点进行优化。
首先实现按tab键切换到命令行窗口,并改变命令行窗口标题栏颜色的功能。
void make_wtitle8(unsigned char *buf, int xsize, char *title, char act)
{
static char closebtn[14][16] = {
"OOOOOOOOOOOOOOO@",
"OQQQQQQQQQQQQQ$@",
"OQQQQQQQQQQQQQ$@",
"OQQQ@@QQQQ@@QQ$@",
"OQQQQ@@QQ@@QQQ$@",
"OQQQQQ@@@@QQQQ$@",
"OQQQQQQ@@QQQQQ$@",
"OQQQQQ@@@@QQQQ$@",
"OQQQQ@@QQ@@QQQ$@",
"OQQQ@@QQQQ@@QQ$@",
"OQQQQQQQQQQQQQ$@",
"OQQQQQQQQQQQQQ$@",
"O$$$$$$$$$$$$$$@",
"@@@@@@@@@@@@@@@@"
};
int x, y;
char c, tc, tbc;
if (act != 0) {
tc = COL8_FFFFFF;
tbc = COL8_000084;
} else {
tc = COL8_C6C6C6;
tbc = COL8_848484;
}
boxfill8(buf, xsize, tbc, 3, 3, xsize - 4, 20);
putfonts8_asc(buf, xsize, 24, 4, tc, title);
for (y = 0; y < 14; y++) {
for (x = 0; x < 16; x++) {
c = closebtn[y][x];
if (c == '@') {
c = COL8_000000;
} else if (c == '$') {
c = COL8_848484;
} else if (c == 'Q') {
c = COL8_C6C6C6;
} else {
c = COL8_FFFFFF;
}
buf[(5 + y) * xsize + (xsize - 21 + x)] = c;
}
}
return;
}
以上函数在act为0与不为0时展示两种不同的颜色。同时在主程序处理键盘中断的部分也需要进行修改:
……
if (i == 256 + 0x0f) { /* Tab */
if (key_to == 0) {
key_to = 1;
make_wtitle8(buf_win, sht_win->bxsize, "task_a", 0);
make_wtitle8(buf_cons, sht_cons->bxsize, "console", 1);
} else {
key_to = 0;
make_wtitle8(buf_win, sht_win->bxsize, "task_a", 1);
make_wtitle8(buf_cons, sht_cons->bxsize, "console", 0);
}
sheet_refresh(sht_win, 0, 0, sht_win->bxsize, 21);
sheet_refresh(sht_cons, 0, 0, sht_cons->bxsize, 21);
}
……
当按下tab键时,修改key_to的值,该变量的值用于确定键盘输入发送到哪里。key_to为0时发送到任务A,为1时则发送到命令行窗口任务,同时改变对应窗口标题栏的颜色。
这样运行之后,按下tab键,console与taks_a窗口的标题栏颜色会进行切换。
但是此时输入仍然出现在task_a窗口中,我们需要继续优化,使键盘能够在console窗口中输入。
实现向console窗口中输入,只需要按下键盘时向console任务的FIFO中发送数据即可。目前每个任务都需要自己的FIFO,这样我们修改结构体TASK的定义,将FIFO与任务绑定。
struct TASK {
int sel, flags; /
int level, priority;
struct FIFO32 fifo;
struct TSS32 tss;
};
接下来需要在主程序中根据key_to的值将数据写入不同的FIFO。
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 && keytable[i - 256] != 0) { /* �ʏ핶�� */
if (key_to == 0) { /* 发送给任务A */
if (cursor_x < 128) {
/* 显示一个字符后将光标后移一位 */
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;
}
} else { /* 发送给命令行窗口 */
fifo32_put(&task_cons->fifo, keytable[i - 256] + 256);
}
}
不仅是普通的字符,还要注意Backspace键的处理。
if (i == 256 + 0x0e) { /* Backspace键 */
if (key_to == 0) { /* 发送给任务A */
if (cursor_x > 8) {
/* 用空白擦除光标后将光标后移一位 */
putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, " ", 1);
cursor_x -= 8;
}
} else { /* 发送给命令行窗口 */
fifo32_put(&task_cons->fifo, 8 + 256);
}
}
console_task任务也需要进行修改,使任务能够接收并处理键盘数据:
void console_task(struct SHEET *sheet)
{
struct TIMER *timer;
struct TASK *task = task_now();
int i, fifobuf[128], cursor_x = 16, cursor_c = COL8_000000;
char s[2];
fifo32_init(&task->fifo, 128, fifobuf, task);
timer = timer_alloc();
timer_init(timer, &task->fifo, 1);
timer_settime(timer, 50);
/* 显示提示符> */
putfonts8_asc_sht(sheet, 8, 28, COL8_FFFFFF, COL8_000000, ">", 1);
for (;;) {
io_cli();
if (fifo32_status(&task->fifo) == 0) {
task_sleep(task);
io_sti();
} else {
i = fifo32_get(&task->fifo);
io_sti();
if (i <= 1) {
if (i != 0) {
timer_init(timer, &task->fifo, 0);
cursor_c = COL8_FFFFFF;
} else {
timer_init(timer, &task->fifo, 1);
cursor_c = COL8_000000;
}
timer_settime(timer, 50);
}
if (256 <= i && i <= 511) { /*
if (i == 8 + 256) {
/* Backspace键 */
if (cursor_x > 16) {
/* 用空白擦除光标后将光标后移一位 */
putfonts8_asc_sht(sheet, cursor_x, 28, COL8_FFFFFF, COL8_000000, " ", 1);
cursor_x -= 8;
}
} else {
/* 一般字符 */
if (cursor_x < 240) {
/* 显示一个字符后将光标后移一位 */
s[0] = i - 256;
s[1] = 0;
putfonts8_asc_sht(sheet, cursor_x, 28, COL8_FFFFFF, COL8_000000, s, 1);
cursor_x += 8;
}
}
}
/* 重新显示光标 */
boxfill8(sheet->buf, sheet->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43);
sheet_refresh(sheet, cursor_x, 28, cursor_x + 8, 44);
}
}
}
这里与主程序的处理基本相同,增加了命令行窗口起始位置的提示符">“,并且对删除的界限做了限制,以防提示符被删除。运行结果如下:
好了,当前可用向命令行窗口中输入数字、字母和一些符号了,但还无法输入”!"与“%”,下面来解决这个问题。
2. 输入符号和大小写字母
要输入“!”和“%”,我们首先要对Shift键进行处理。
- 左Shift:按下:0x2a 抬起:0xaa
- 右Shift:按下:0x35 抬起:0xb6
我们准备一个key_shift变量,当左Shift按下时置为1,右Shift按下时置为2,同时按下置为3,都没有按下置为0;
准备两个keytable表,当key_shift为0时,用keytable0[]将按键编码转换为字符编码;当key_shift不为0时,使用keytbale[]进行转换。
static char keytable0[0x80] = {
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', '.', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0x5c, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x5c, 0, 0
};
static char keytable1[0x80] = {
0, 0, '!', 0x22, '#', '$', '%', '&', 0x27, '(', ')', '~', '=', '~', 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', '.', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, '_', 0, 0, 0, 0, 0, 0, 0, 0, 0, '|', 0, 0
};
int key_to = 0, key_shift = 0;
……
for (;;) {
io_cli();
if (fifo32_status(&fifo) == 0) {
task_sleep(task_a);
io_sti();
} 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 < 0x80 + 256) { /* 将按键编码转换为字符编码 */
if (key_shift == 0) {
s[0] = keytable0[i - 256];
} else {
s[0] = keytable1[i - 256];
}
} else {
s[0] = 0;
}
if (s[0] != 0) { /* 一般字符 */
if (key_to == 0) { /* 发送给任务A */
if (cursor_x < 128) {
/* 显示一个字符光标后移一位 */
s[1] = 0;
putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, s, 1);
cursor_x += 8;
}
} else { /* 发送给命令行窗口 */
fifo32_put(&task_cons->fifo, s[0] + 256);
}
}
if (i == 256 + 0x0e) { /* Backspace键 */
if (key_to == 0) {
if (cursor_x > 8) {
putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, " ", 1);
cursor_x -= 8;
}
} else {
fifo32_put(&task_cons->fifo, 8 + 256);
}
}
if (i == 256 + 0x0f) { /* Tab */
if (key_to == 0) {
key_to = 1;
make_wtitle8(buf_win, sht_win->bxsize, "task_a", 0);
make_wtitle8(buf_cons, sht_cons->bxsize, "console", 1);
} else {
key_to = 0;
make_wtitle8(buf_win, sht_win->bxsize, "task_a", 1);
make_wtitle8(buf_cons, sht_cons->bxsize, "console", 0);
}
sheet_refresh(sht_win, 0, 0, sht_win->bxsize, 21);
sheet_refresh(sht_cons, 0, 0, sht_cons->bxsize, 21);
}
if (i == 256 + 0x2a) { /* 左Shift ON */
key_shift |= 1;
}
if (i == 256 + 0x36) { /* 右Shift ON */
key_shift |= 2;
}
if (i == 256 + 0xaa) { /* 左Shift OFF */
key_shift &= ~1;
}
if (i == 256 + 0xb6) { /* 右Shift OFF */
key_shift &= ~2;
}
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);
if ((mdec.btn & 0x01) != 0) {
sheet_slide(sht_win, mx - 80, my - 8);
}
}
} else if (i <= 1) {
if (i != 0) {
timer_init(timer, &fifo, 0);
cursor_c = COL8_000000;
} else {
timer_init(timer, &fifo, 1);
cursor_c = COL8_FFFFFF;
}
timer_settime(timer, 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);
}
}
}
}
这样就实现了一些特殊字符的输入:
不过到目前位置我们输入的都只是大写字母,如何输入小写字母呢?
要输入小写字母,我们需要同时判断Shift与CapsLock的状态。
输入小写字母的条件,总结下来就是
- 输入的字符为英文字母
- “CapsLock为Off & Shift键为Off”或“CapsLock为On & Shift键为On”
CapsLock的状态我们已经从BIOS获取并且保存起来了,现在只需要使用即可。
- binfo->leds的第4位: ScrollLock状态
- binfo->leds的第5位: NumLock状态
- binfo->leds的第6位:CapsLock状态
这样我们就可以支持小写字母的输入了:
……
if ('A' <= s[0] && s[0] <= 'Z') { /* 输入字符位应为字母 */
if (((key_leds & 4) == 0 && key_shift == 0) ||
((key_leds & 4) != 0 && key_shift != 0)) {
s[0] += 0x20; /*将大写字母转换为小写字母 */
}
}
……
在我们使用的ASCII码中,只要将大写字母的编码加上0x20即可得到小写字母的编码。
既然已经提到了这些锁定键,接下来就更进一步,实现对这些锁定键的支持吧。
3. 支持锁定键
已经知道了这些锁定键的按键编码,接收到这些编码时只需要将binfo->leds中的相应内容改写就可以了。
但是目前还没有实现与键盘上控制灯的同步,可能会出现键盘上的CapsLock灯没有亮起,却处于CapsLock模式,这个问题还是需要解决一下。
关于NumLock与CapsLock等LED的控制,可以采用如下的方法向键盘发送指令和数据:
- 读取状态寄存器,等待bit1的值变为0
- 向数据输出(0060)写入要发送的一个字节数据
- 等待键盘返回一个字节的信息,与等待键盘输入所采用的方法相同(IRQ等待或者用轮询状态寄存器bit1的值直到其变为0)
- 返回的信息如果为0xfa,表明一个字节的数据已经成功发送给键盘,如果为0xfe则表明发送失败,需要返回第一步重新发送
要控制LED的状态,需要按上述方法执行两次,向键盘发送EDxx数据。其中xx的bit0代表ScrollLock,bit1代表NumLock,bit2代表CapsLock(0表示熄灭,1表示点亮),bit3-7为保留位,置为0即可。
根据以上信息对程序进行修改:
……
/* 为了避免和键盘当前状态冲突,在一开始先进行设置 */
fifo32_put(&keycmd, KEYCMD_LED);
fifo32_put(&keycmd, key_leds);
……
if (fifo32_status(&keycmd) > 0 && keycmd_wait < 0) {
/* 如果存在向键盘控制器发送的数据,则发送 */
keycmd_wait = fifo32_get(&keycmd);
wait_KBC_sendready();
io_out8(PORT_KEYDAT, keycmd_wait);
}
……
if (i == 256 + 0x3a) { /* CapsLock */
key_leds ^= 4;
fifo32_put(&keycmd, KEYCMD_LED);
fifo32_put(&keycmd, key_leds);
}
if (i == 256 + 0x45) { /* NumLock */
key_leds ^= 2;
fifo32_put(&keycmd, KEYCMD_LED);
fifo32_put(&keycmd, key_leds);
}
if (i == 256 + 0x46) { /* ScrollLock */
key_leds ^= 1;
fifo32_put(&keycmd, KEYCMD_LED);
fifo32_put(&keycmd, key_leds);
}
if (i == 256 + 0xfa) { /* 键盘成功接收到数据 */
keycmd_wait = -1;
}
if (i == 256 + 0xfe) { /* 键盘未成功接收到数据 */
wait_KBC_sendready();
io_out8(PORT_KEYDAT, keycmd_wait);
}
这里创建了一个keycmd的FIFO缓冲区,用来管理任务A向键盘控制器发送数据的顺序。如果有数据要发送给键盘控制器,会先在keycmd中累积起来。
keycmd_wait变量用来表示向键盘控制器发送数据的状态。当keycmd_wai的值为-1时,表示键盘控制器处于通常状态,可以发送指令;当值不为-1时,表示键盘控制器正在等待发送的数据,这是要被发送的数据被保存在keycmd_wait变量中。
在for循环的开头,当keycmd中有数据且keycmd_wait为-1时,向键盘发送一个字节的数据,在开始发送数据的同时,keycmd_wait变为非-1的值。随后,当从键盘接收到0xfa的返回值时,keycmd_wait恢复为-1,继续发送下一个数据。当键盘接收到的返回值为0xfe时,则重新发送刚才的数据。
这样锁定键的支持就完成了。但实际通过虚拟机运行还是会与设计不符。作者在真机上运行是正常的,推测可能原因是虚拟机的键盘灯仍然是由Windows控制的,而不是虚拟机控制的。
本篇内容比较简单,也更为可视化,算是学习多任务之后的一个调剂吧。下一篇继续命令行窗口,敬请期待。
标签:task,sht,COL8,16,30,cursor,读书笔记,cons,key From: https://blog.csdn.net/Ocean1994/article/details/141949571