[Rx86OS-XVII] 制作命令行窗口I(键盘输入)

平台

处理器:Intel Celeron® Dual-Core CPU  2.10GHz

操作系统:Windows7 专业版 x86

阅读书籍:《30天自制操作系统》—川合秀实[2015.04.22]

工具:../toolset/


1 显示命令行窗口

单独创建一个新任务,在新任务中制作命令行窗口。修改HariMain。

void HariMain(void)
{
    ……
    //sht_console
	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; //任务对应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,优先级为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);//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); //置0让光标显示白色
					cursor_c = COL8_FFFFFF;
				} else {
					timer_init(timer, &fifo, 1);//置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);
			}
		}
	}
}

Figure1. 命令行窗口雏形

在主函数内,通过往显存内写调色板号的方式显示了命令行窗口,并设置命令行窗口所在的图层,然后创建一个新任务来操作命令行窗口。此时只有task_a能够接受键盘输入。


2 切换输入窗口

使用Tab按键让绘制出的命令行窗口能够接受键盘输入。


2.1 改变Tab切换窗口的标题栏颜色

将窗口标题栏显示写成单独的函数。然后在HariMain()函数中设置一个变量key_to,用key_to标志当前Tab键该切换哪个窗口的标题栏颜色。


2.2 切换窗口接受键盘字符输入

每个任务都对应一个缓冲区。如果Tab切换到命令行窗口时,只要向命令行窗口所在任务的缓冲区发送键盘输入(CPU管理的任务管不了中断产生),再将缓冲区内的键盘数据显示在命令行窗口中,这样就实现了命令行窗口接受键盘输入。采用相同方式让其它窗口接受键盘输入。


更改描述任务的数据结构。

struct TASK {
	int sel, flags;
	int level, priority;
	struct FIFO32 fifo; //每个任务管理缓冲区的结构体
	struct TSS32 tss;
};

改写HariMain,判断key_to的值让其往不同任务(2个)的缓冲区内发送键盘数据。

void HariMain(void)
{
    ……
    for (;;) {
		io_cli();
		if (fifo32_status(&fifo) == 0) {
			……
		} else {
			i = fifo32_get(&fifo);
			io_sti();
			if (256 <= i && i <= 511) { //键盘数据
				……
				if (i < 0x54 + 256 && keytable[i - 256] != 0) {//字符
					if (key_to == 0) {//发送给task_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);
					}
				}
				if (i == 256 + 0x0e) {	//退格键
					if (key_to == 0) {//发送给task_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);
					}
				}
				……
			} else if (i <= 1) { //光标定时
				……
		}
	}
}

修改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);//置0显示白色
					cursor_c = COL8_FFFFFF;
				} else {
					timer_init(timer, &task->fifo, 1); //置1显示黑色
					cursor_c = COL8_000000;
				}
				timer_settime(timer, 50);
			}
			if (256 <= i && i <= 511) { //键盘字符输入
				if (i == 8 + 256) { //退格键
					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);
		}
	}
}

键盘中断调用的fifo32_put函数的缓冲区参数可以设成每个任务的缓冲区。每个任务的缓冲区在内存中独立存在,任务可以访问到本任务之内的缓冲区而访问不到其它任务内的缓冲。

[Rx86OS-XVII] 制作命令行窗口I(键盘输入)_第1张图片

Figure2. 多窗口切换接受键盘字符输入

2.3 切换窗口接受键盘符号输入

如果要接收键盘的’!’,’@’等符号就需要处理Shift键。Shift按键的编码如下:


“书”在程序中定义一个变量key_shift来记录2个Shift键的状态,左Shift按下时key_shift为1,右Shift按下时key_shift为1,2个都不按时key_shift为0。然后再准备两个数组,keytable0保存键盘上的字符(见键盘输入处理),keytable1保存特殊的符号。然后程序根据缓冲区内的数据范围来决定使用哪一个数据显示键盘输入。(harib14e)。


2.4 区分大小写字符输入

要实现区分大小写字母的输入,必须判断Shift键以及CapsLock的状态。输入小写字母的情况:输入英文字母,CapsLock为OFF& Shift键为OFF或者CapsLock为ON& Shift键为ON。在asmhead.nas中已经调用BIOS程序获取了键盘状态,[LEDS]内存的第4位保存ScrollLock状态,第5位保存NumLock的状态,第6位保存的CapsLock状态。根据这几位具体的含义编写程序就能够显示出大小字母。(CaosLock编码为0x3a;Numlock编码为0x45;ScrollLock编码为0x46)


2.5 支持锁定按键

当收到锁定按键的编码时,需要将[LEDS]中的数据进行改写,这样按键锁定的模式就可以发生切换,但这些按键对应的LED灯的状态却不会改变。关于LED的控制:

  • 对于NumLock和CapsLock等的LED的控制,可采用下面的方法向键盘发送指令和数据。
  •         读取状态寄存器,等待bit1的值变为0.
  •         向数据输出(0060)写入要发送的1个字节数据。
  •         等待键盘返回1个字节的信息,这个等待键盘输入所采用的方法相同(用IRQ等待或者用轮训状态寄存器bit1的值直到其变为0都可以)
  •         返回的信息如果为oxfa,表明1个字节的数据已成功发送给键盘。如为0xfe则表明发送失败,需要返回第一步重新发送。
  • 要控制LED的状态,需要按上述方法执行两次,向键盘发送EDxx数据。其中,xx的bit0代表ScrollLock,bit1代表NumLock,bit2代表CapsLock(0表示熄灭,1表示点亮)。bit3 ~ 7为保留位,置0即可。

根据以上信息编写代码。

#define KEYCMD_LED		0xed

void HariMain(void)
{
    ……
    struct FIFO32 fifo, keycmd;
    int fifobuf[128], keycmd_buf[32];
    ……
    int key_to = 0, key_shift = 0, key_leds = (binfo->leds >> 4) & 7, keycmd_wait = -1;
    ……
    fifo32_init(&keycmd, 32, keycmd_buf, 0);
    ……
    //避免冲突,一开始进行设置一下
    fifo32_put(&keycmd, KEYCMD_LED);
    fifo32_put(&keycmd, key_leds);

    for (;;) {
		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);
				}
              ……
   }
}

马虎读。


总结

认识所操作对象(硬件)各模块被设计的关联性(独立还是有关联)。

改变笔记风格,不再全贴源码,记录思路。


[x86OS] Note Over.

[2015.04.29]

你可能感兴趣的:([Rx86OS-XVII] 制作命令行窗口I(键盘输入))