Day18

今天继续完善命令行窗口。

一些感想以及优化

可改进的光标闪烁

因为目前只有两个窗口,所以可以通过按tab来判断当前是哪个窗口在执行,从而每个任务都要判断是否该显示光标。但是这样其实不太好,因为每个任务里还要和别的任务产生一些关联(这个应该是不必要的)。个人感觉比较好的方法是当这个任务被唤醒的时候,显示光标,休眠的时候则停止光标的计时。不过目前看起来2个窗口的时候,这么写也没问题(而且貌似这两个都是在前台活动的窗口)。那就先按照这样写吧。估计以后窗口多了,作者又要自己改了。

回车与窗口滚动

本质上都是对像素进行的操作。所以待会直接看代码应该就足够了

文件存储方式

当写到dir命令的时候,就不得不知道文件在磁盘里是怎么存储的:

struct FILEINFO
{
    unsigned char name[8], ext[3], type;
    char reserve[10];
    unsigned short time, date, clustno;
    unsigned int size;
};

name: 8字节文件名,全大写。不足时用空格补足(超过没说该怎么办)
若第一个字节为0xe5,则代表文件被删除
若第一个字节为0x00,则代表不包含任何文件名信息
ext:3字节扩展名,全大写。不足用空格补足
type:文件的属性信息

  • 0x00 一般文件
  • 0x01 只读
  • 0x02 隐藏
  • 0x04 系统
  • 0x08 非文件信息
  • 0x10 目录
  • 0x20 一般文件

文件多种方式可以或起来(上面都是可以位或的)
reserve: 10字节保留。Windows定义的
time: WORD整数,存放文件时间
date: WORD整数,存放文件日期
clustno:(簇号) WORD整数,文件的内容从磁盘上的哪个扇区开始存放。
size: DWORD整数,存放文件大小

对于命令的改进比较方法

本节目前只加了三个命令:mem、cls、dir。但是可想而知命令多了以后,光if比较就会判断上很长时间。这个点以后也可以优化,改成哈希+函数指针什么的应该会比较好。

因为今天还是初步的命令行,各种功能还不太完善。所以想到了两个目前看起来需要优化的点。看看以后会是怎么样。

控制光标闪烁(1)

harib15a

作者通过tab键的切换,来判断当前窗口是否应该闪烁光标。先从HariMain窗口的光标开始。
对于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);
        cursor_c = -1; // 不显示光标
        boxfill8(sht_win->buf, sht_win->bxsize, COL8_FFFFFF, cursor_x, 28, cursor_x + 7, 43);
    }
    else
    {
        key_to = 0;
        make_wtitle8(buf_win,  sht_win->bxsize,  "task_a",  1);
        make_wtitle8(buf_cons, sht_cons->bxsize, "console", 0);
        cursor_c = COL8_000000; // 显示光标
    }
    sheet_refresh(sht_win,  0, 0, sht_win->bxsize,  21);
    sheet_refresh(sht_cons, 0, 0, sht_cons->bxsize, 21);
}

如果状态切换了,需要重新显示光标

if(cursor_c >= 0)
{
    boxfill8(sht_win->buf, sht_win->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43);
}

定时器部分,如果cursor当前是-1的话,就不要绘图

if (i <= 1) { /* カーソル用タイマ */
    if (i != 0) {
        timer_init(timer, &fifo, 0); /* 次は0を */
        if (cursor_c >= 0) {
            cursor_c = COL8_000000;
        }
    } else {
        timer_init(timer, &fifo, 1); /* 次は1を */
        if (cursor_c >= 0) {
            cursor_c = COL8_FFFFFF;
        }
    }
    timer_settime(timer, 50);
    if (cursor_c >= 0) {
        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);
    }
}

这个是作者写的光标定时器部分,但是这么多cursorc >= 0不太爽,所以改一下顺序,其实也差不多

if(i <= 1) // 光标用定时器
{
    timer_init(timer, &fifo, 0); // 随便置0
    timer_settime(timer, 50);
    if(cursor_c >= 0)
    {
        if(i != 0)
        {
            // 0 已经被置过了
            cursor_c = COL8_000000;
        }
        else
        {
            timer_init(timer, &fifo, 1); // 下次置1(覆盖了)
            cursor_c = COL8_FFFFFF;
        }
        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);
    }
}

语句之间的顺序和之前会不太一样。。但是估计也没什么太大改变吧。。
修改完HariMain的光标,然后是console的

控制光标闪烁(2)

harib15b

为了让console知道自己该不该闪烁,通过Harimain向其FIFO里传递信息。。。

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);
        cursor_c = -1; // 不显示光标
        boxfill8(sht_win->buf, sht_win->bxsize, COL8_FFFFFF, cursor_x, 28, cursor_x + 7, 43);
        fifo32_put(&task_cons->fifo, 2); // 命令行窗口光标ON
    }
    else
    {
        key_to = 0;
        make_wtitle8(buf_win,  sht_win->bxsize,  "task_a",  1);
        make_wtitle8(buf_cons, sht_cons->bxsize, "console", 0);
        cursor_c = COL8_000000; // 显示光标
        fifo32_put(&task_cons->fifo, 3); // 命令行窗口光标OFF
    }
    sheet_refresh(sht_win,  0, 0, sht_win->bxsize,  21);
    sheet_refresh(sht_cons, 0, 0, sht_cons->bxsize, 21);
}

主函数加了两行,然后是console

if(i <= 1) // 光标用定时器
{
    if(i != 0)
    {
        timer_init(timer, &task->fifo, 0); // 下次置为0
        if(cursor_c >= 0)
        {
            cursor_c = COL8_FFFFFF;
        }
    }
    else
    {
        timer_init(timer, &task->fifo, 1); // 下次置为1
        if(cursor_c >= 0)
        {
            cursor_c = COL8_000000;
        }
    }
    timer_settime(timer, 50);
}
if(i == 2) // 光标ON
{
    cursor_c = COL8_FFFFFF;
}
if(i == 3) // 光标OFF
{
    boxfill8(sheet->buf, sheet->bxsize, COL8_000000, cursor_x, 28, cursor_x + 7, 43);
    cursor_c = -1;
}

为了稳定性(省事),这里就不魔改代码逻辑了。

光标的问题到这里就解决好了。

对回车键的支持

harib15c

回车键的响应方式,就是画一个换行,然后改变当前光标的坐标。
首先,当接收到’\n’时(ASCII值为10,键盘输入为0x1c),主程序向控制台FIFO发送一个数据

if(i == 256 + 0x1c) // 回车键
{
    if(key_to != 0) // 发送至命令行窗口
    {
        fifo32_put(&task_cons->fifo, 10 + 256);
    }
}

控制台接收到数据之后,进行重绘操作

if(i == 10 + 256) // 回车键
{
    if(cursor_y < 28 + 112)
    {
        // 用空格将光标擦除
        putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF, COL8_000000, " ", 1);
        cursor_y += 16;
        // 显示提示符
        putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, ">", 1);
        cursor_x = 16;
    }
}

对窗口滚动的支持

harib15d

元素上移+最下面一行涂黑

if(i == 10 + 256)
{
    // Enter
    // 用空格将光标擦除
    putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF, COL8_000000, " ", 1);
    if(cursor_y < 28 + 112)
    {
        cursor_y += 16; // 换行
    }
    else
    {
        // 滚动
        for(y = 28; y < 28 + 112; ++y)
        {
            for(x = 8; x < 8 + 240; ++x)
            {
                sheet->buf[x + y * sheet->bxsize] = sheet->buf[x + (y + 16) * sheet->bxsize];
            }
        }
        for(y = 28 + 112; y < 28 + 128; ++y)
        {
            for(x = 8; x < 8 + 240; ++x)
            {
                sheet->buf[x + y * sheet->bxsize] = COL8_000000;
            }
        }
        sheet_refresh(sheet, 8, 28, 8 + 240, 28 + 128);
    }
    // 显示提示符
    putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, ">", 1);
    cursor_x = 16;
}

没什么好说的)

mem命令

harib15e

之前只是涉及了图像的改变,所以现在要把字符也记录下来,方便我们进行指令判断。
对于一般字符,把它记录到缓冲区里:

// 一般字符
if(cursor_x < 240)
{
    // 显示一个字符之后将光标后移一位
    s[0] = i - 256;
    s[1] = 0;
    cmdline[cursor_x / 8 - 2] = i - 256;
    putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF, COL8_000000, s, 1);
    cursor_x += 8;
}

cmdline是缓冲区,用于记录当行的字符。
记录完成之后,当遇到换行的时候,就要进行指令的判断

if(i == 10 + 256)
{
    // Enter
    // 用空格将光标擦除
    putfonts8_asc_sht(sheet, cursor_x, cursor_y, COL8_FFFFFF, COL8_000000, " ", 1);
    cmdline[cursor_x / 8 - 2] = 0;
    cursor_y = cons_newline(cursor_y, sheet);
    // 执行命令
    if(cmdline[0] == 'm' && cmdline[1] == 'e' && cmdline[2] == 'm' && cmdline[3] == 0)
    {
        // mem命令
        sprintf(s, "total   %dMB", memtotal / (1024 * 1024));
        putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, s, 30);
        cursor_y = cons_newline(cursor_y, sheet);
        sprintf(s, "free %dKB", memman_total(memman) / 1024);
        putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, s, 30);
        cursor_y = cons_newline(cursor_y, sheet);
        cursor_y = cons_newline(cursor_y, sheet);
    }
    else if(cmdline[0] != 0)
    {
        // 不是命令,也不是空行
        putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, "Bad command.", 12);
        cursor_y = cons_newline(cursor_y, sheet);
        cursor_y = cons_newline(cursor_y, sheet);
    }
    // 显示提示符
    putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, ">", 1);
    cursor_x = 16;
}

但是,memtotal也需要从主函数传进来。这样的话就只能再在主函数里修改栈了。

task_cons->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 12;
*((int *) (task_cons->tss.esp + 4)) = (int) sht_cons;
*((int *) (task_cons->tss.esp + 8)) = memtotal;

之前是-8,现在是-12了。
为了方便理解,新换行(包括滚动)也写成了一个函数,就是把之前的复制到这里来。

int cons_newline(int cursor_y, struct SHEET *sheet)
{
    int x, y;
    if(cursor_y < 28 + 112)
    {
        cursor_y += 16; // 换行
    }
    else
    {
        // 滚动
        for(y = 28; y < 28 + 112; ++y)
        {
            for(x = 8; x < 8 + 240; ++x)
            {
                sheet->buf[x + y * sheet->bxsize] = sheet->buf[x + (y + 16) * sheet->bxsize];
            }
        }
        for(y = 28 + 112; y < 28 + 128; ++y)
        {
            for(x = 8; x < 8 + 240; ++x)
            {
                sheet->buf[x + y * sheet->bxsize] = COL8_000000;
            }
        }
        sheet_refresh(sheet, 8, 28, 8 + 240, 28 + 128);
    }
    return cursor_y;
}

接着搞下一条指令

cls命令

harib15f

把之前的比较改成strcmp来比较。

if(strcmp(cmdline, "cls") == 0)
{
    // cls命令
    for(y = 28; y < 28 + 128; ++y)
    {
        for(x = 8; x < 8 + 240; ++x)
        {
            sheet->buf[x + y * sheet->bxsize] = COL8_000000;
        }
    }
    sheet_refresh(sheet, 8, 28, 8 + 240, 28 + 128);
    cursor_y = 28;
}

也没什么问题。。

dir命令

harib15g

在了解dir命令之前,先要知道文件是怎么存放的。。(参考最上面)
然后因为我们一开始也把10个柱面加载到了内存中(0x00102600开始),
然后,定义这个结构体

struct FILEINFO
{
    unsigned char name[8], ext[3], type;
    char reserve[10];
    unsigned short time, date, clustno;
    unsigned int size;
};

确定指针位置

#define ADR_DISKIMG        0x00100000
if(strcmp(cmdline, "dir") == 0)
{
    // dir命令
    for (x = 0; x < 224; x++)
    {
        if(finfo[x].name[0] == 0x00)
        {
            break;
        }
        if(finfo[x].name[0] != 0xe5)
        {
            if((finfo[x].type & 0x18) == 0)
            {
                sprintf(s, "filename.ext   %7d", finfo[x].size); // 先写个默认的
                for(y = 0; y < 8; ++y)
                {
                    s[y] = finfo[x].name[y];
                }
                s[9] = finfo[x].ext[0];
                s[10] = finfo[x].ext[1];
                s[11] = finfo[x].ext[2];
                putfonts8_asc_sht(sheet, 8, cursor_y, COL8_FFFFFF, COL8_000000, s, 30);
                cursor_y = cons_newline(cursor_y, sheet);
            }
        }
    }
    cursor_y = cons_newline(cursor_y, sheet);
}

这样就做好了3个命令。今天比起前三天的多任务来说还是简单好多了)