Day17

今天继续完善了一下多任务,之后是支持键盘的各种输入。

一些感想和问题

闲置任务

闲置任务是为了保证所有前台的任务都休眠的时候,系统不至于无法切换到一个任务而出错。把它注册到最下层即可。(Windows下也有system idle process)

void task_idle(void)
{
    for(;;)
    {
        io_hlt();
    }
}

一个bug

在控制台输入的时候,输入第一个字符,虽然已经释放了,但是就像是一直按着一样,蹦出来一串相同的字符。而且后序按什么键都不起效果。
经过检查,发现是中文输入法开了)虽然不知道中文输入法会和这个虚拟机、或者操作系统起什么冲突,但是还是挺奇怪的。。

目前创建的是命令行的第一步:支持各种符号的输入

闲置任务

harib14a

只需要在init的时候,把这个idle放进去就好了。

创建命令行窗口

harib14b

先画出来一个窗口,并且让它目前还不可以接收输入的字符吧
窗口创建:

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 */

调整层叠顺序

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); // 下次置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);
            }
        }
    }
}

因为console需要休眠自己,所以在最开始通过task_now获得了自己的地址。
目前的话还不能输入什么文字,因为还没切换到这个任务来。

切换输入窗口

harib14c

绘制窗口就省略了,看一下tab是怎么切换的:

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的话会绘制不同的窗口(对应的窗口会高亮,另一个则会变暗)

实现字符输入

harib14d

因为task基本上都会用到IO,所以把FIFO也放到task里了

struct TASK
{
    int sel, flags; // sel代表GDT的编号
    int level, priority;
    struct FIFO32 fifo;
    struct TSS32 tss;
};

然后再Harimain和console里,都要进行对应的修改

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);
        }
    }
    if(i == 256 + 0x0e) // 退格键
    {
        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);
        }
    }
    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);
    }
    // 重新显示光标
    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);
}

根据key_to不同,往console或者是taskA的FIFO发字符
console的键盘处理部分的话,因为和之前的HariMain挺相似的,所以就不再写了。

符号的输入

harib14e

输入符号的话,就要处理shift键。所以需要两张表,一张对应没按shift,一张对应按了shift。不过因为作者是日式键盘,所以有些地方好像对应不太上。之后再修改好了。
shift的状态对应不同的输出

if(key_shift == 0)
{
    s[0] = keytable0[i - 256];
}
else
{
    s[0] = keytable1[i - 256];
}

进入和退出状态:

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

这就处理好了shift,接下来就可以进行大小写了

大写字母与小写字母输入

harib14f

切换大小写,既可以通过按shift,也可以通过capslock。锁定键状态储存在了binfo->leds中
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; // 将大写字母转换为小写字母
    }
}

接下来是锁定键

对各种锁定键的支持

harib14g

按键编码其实很熟悉,但是因为一般的锁定键上面都有灯,要让等亮灭的话,需要进行额外的操作。
简而言之,等待状态寄存器变为0之后,要先向0x0060发送0xed(说明要设置LED)。再等待状态寄存器变为0之后,发送1字节的数据。如果返回为0xfa,说明成功发送;如果返回0xfe,需要重新发送。
于是程序这么写:
等待部分:

if(fifo32_status(&keycmd) > 0 && keycmd_wait < 0)
{
    // 如果存在向键盘控制器发送的数据,则发送它
    keycmd_wait = fifo32_get(&keycmd);
    wait_KBC_sendready();
    io_out8(PORT_KEYDAT, keycmd_wait);
}

keycmd是一个缓冲区,用于顺序发送数据。
更改部分:

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

这样各种输入就支持了。。