第24天 窗口操作

第24天 窗口操作

2020.4.29

1. 窗口切換(1)(harib21a)

  • 實現能夠切換窗口順序的功能,首先實現從鍵盤切換的方法:當按下F11時,將最下面的窗口放到最上面

  • F11的按鍵編碼是0x57,F12的按鍵編碼是0x58

  • 修改HariMain:

    void HariMain(void)
    {
        ……
        for (;;) {
            ……
            if (fifo32_status(&fifo) == 0) {
                ……
            } else {
                ……
                if (256 <= i && i <= 511) { /* 鍵盤數據 */
                    ……
                    if (i == 256 + 0x57 && shtctl->top > 2) {	/* F11 */
                        sheet_updown(shtctl->sheets[1], shtctl->top - 1);
                    }
                   ……
                } else if (512 <= i && i <= 767) { /* 鼠標數據 */
                    ……
                } else if (i <= 1) { /* 光標定時器 */
                    ……
                }
            }
        }
    }
    
    • 其實就添加了三行代碼。
    • 核心代碼sheet_updown(shtctl->sheets[1], shtctl->top - 1);的意思是:將高度爲1的圖層(從下面數第2個圖層,第1個圖層高度爲0是背景圖層)的高度提升到top-1(從上面數第2個圖層,第1個數鼠標圖層)。
  • make後用VMware運行:

2. 窗口切換(2)(harib21b)

  • 在harib21a中實現了使用鍵盤切換窗口的功能,現在實現使用鼠標進行窗口圖層切換的功能

  • 首先,現需要將鼠標左鍵按下後移動task_a窗口的代碼刪除。使用鼠標移動窗口的功能很快就會實現。

  • 修改HariMain:

    void HariMain(void)
    {
        ……
        for (;;) {
            ……
            if (fifo32_status(&fifo) == 0) {
                ……
            } else {
                ……
                if (256 <= i && i <= 511) { /* 鍵盤數據 */
                   ……
                } else if (512 <= i && i <= 767) { /* 鼠標數據 */
                    if (mouse_decode(&mdec, i - 512) != 0) {
                        /* 鼠標指針移動 */
                        ……
                        if ((mdec.btn & 0x01) != 0) {
                            /* 鼠標左鍵按下 */
                            /* 按照從上到下的順序尋找鼠標所指向的圖層 */
                            for (j = shtctl->top - 1; j > 0; j--) {
                                sht = shtctl->sheets[j];
                                x = mx - sht->vx0; /*x是相對於圖層的X方向座標*/
                                y = my - sht->vy0; /*y是相對於圖層的Y方向座標*/
                                if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) { /*鼠標位置(mx, my)位於圖層內*/
                                    if (sht->buf[y * sht->bxsize + x] != sht->col_inv) { /*鼠標位置所在的圖層的像素不是透明*/
                                        sheet_updown(sht, shtctl->top - 1); /*將當前圖層高度提升至top-1高度*/
                                        break; /*終止遍歷*/
                                    }
                                }
                            }
                        }
                    }
                } else if (i <= 1) { /* 光標定時器 */
                    ……
                }
            }
        }
    }
    
    • 確定鼠標點擊的是哪個圖層的方法:按照圖層高度,從上到下,判斷鼠標位置位於哪個圖層的範圍內並且保證該位置不是透明色。
  • make後用VMware運行:

    • 如果命令行在最上面的話,由於現在還不能移動圖層,因此會遮擋別的窗口,可以使用F11將遮住的圖層顯示出來。

3. 移動窗口(harib21c)

  • 實現窗口移動的功能:

    • 當鼠標左鍵點擊窗口時,如果點擊位置位於窗口的標題欄區域,則進入窗口移動模式,使窗口的位置隨着鼠標而移動,當鬆開鼠標左鍵的時候,退出窗口移動模式,返回通常模式。
  • 修改HariMain:

    void HariMain(void)
    {
        ……
        int j, x, y, mmx = -1, mmy = -1;
        struct SHEET *sht = 0;
        ……
        for (;;) {
            ……
            if (fifo32_status(&fifo) == 0) {
                ……
            } else {
                ……
                if (256 <= i && i <= 511) { /* 鍵盤數據 */
                    ……
                } else if (512 <= i && i <= 767) { /* 鼠標數據 */
                    if (mouse_decode(&mdec, i - 512) != 0) {
                        /* 鼠標移動 */
                        ……
                        if ((mdec.btn & 0x01) != 0) {
                            /* 按下左鍵 */
                            if (mmx < 0) {
                                /* 如果處於通常模式 */
                                /* 按從上到下的順序尋找鼠標所指向的圖層 */
                                for (j = shtctl->top - 1; j > 0; j--) {
                                    sht = shtctl->sheets[j];
                                    x = mx - sht->vx0;
                                    y = my - sht->vy0;
                                    if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) {
                                        if (sht->buf[y * sht->bxsize + x] != sht->col_inv) {
                                            sheet_updown(sht, shtctl->top - 1);
                                            if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) { /*如果鼠標位置位於標題欄*/
                                                mmx = mx;	/* 進入窗口移動模式 */
                                                mmy = my;   /* 記錄當前鼠標位置 */
                                            }
                                            break;
                                        }
                                    }
                                }
                            } else {
                                /* 如果處於窗口移動模式 */
                                x = mx - mmx;	/* 計算鼠標的移動距離 */
                                y = my - mmy;
                                sheet_slide(sht, sht->vx0 + x, sht->vy0 + y); /*相應地移動圖層*/
                                mmx = mx;	/* 更新mmx,記錄當前鼠標位置 */
                                mmy = my;
                            }
                        } else {
                            /* 沒有按下左鍵 */
                            mmx = -1;	/* 返回通常模式 */
                        }
                    }
                } else if (i <= 1) { /* 光標定時器 */
                    ……
                }
            }
        }
    }
    
    • mmx和mmy中的mm是move mode的縮寫,這兩個座標記錄上一次鼠標的位置,以計算鼠標的位移。規定,當mmx<0時,當前模式不是窗口移動模式
  • make後用VMware運行:

    • 還不錯哦。

4. 用鼠標關閉窗口(harib21d)

  • 以前結束應用程序(關閉窗口)的方式是應用程序本身接收鍵盤數據(回車鍵)或者Shift+F1。

  • 現在使用鼠標關閉應用程序窗口,關閉了窗口並關閉應用程序本身。注意,現在還不能關閉任務,比如命令行還不可以關閉

  • 修改HariMain:

    void HariMain(void)
    {
        ……
        for (;;) {
            ……
            if (fifo32_status(&fifo) == 0) {
                ……
            } else {
                ……
                if (256 <= i && i <= 511) { /* 鍵盤數據 */
                    ……
                } else if (512 <= i && i <= 767) { /* 鼠標數據 */
                    if (mouse_decode(&mdec, i - 512) != 0) {
                        /* 鼠標移動 */
                        ……
                        if ((mdec.btn & 0x01) != 0) {
                            /* 按下左鍵 */
                            if (mmx < 0) {
                                /* 如果處於通常模式 */
                                /* 按照從上到下的順序尋找鼠標指向的圖層 */
                                for (j = shtctl->top - 1; j > 0; j--) {
                                    sht = shtctl->sheets[j];
                                    x = mx - sht->vx0;
                                    y = my - sht->vy0;
                                    if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) {
                                        if (sht->buf[y * sht->bxsize + x] != sht->col_inv) {
                                            sheet_updown(sht, shtctl->top - 1);
                                            if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) {
                                                mmx = mx;	
                                                mmy = my;
                                            }
                                            if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) {
                                                /* 點擊“X”按鈕 */
                                                if (sht->task != 0) {	/* 判斷該窗口是否爲應用程序窗口 */
                                                    cons = (struct CONSOLE *) *((int *) 0x0fec);
                                                    cons_putstr0(cons, "\nBreak(mouse) :\n");
                                                    io_cli();	/* 強制結束應用程序時禁止中斷 */
                                                    task_cons->tss.eax = (int) &(task_cons->tss.esp0);
                                                    task_cons->tss.eip = (int) asm_end_app;
                                                    io_sti();
                                                }
                                            }
                                            break;
                                        }
                                    }
                                }
                            } else {
                                /* 如果處於窗口移動模式 */
                                ……
                            }
                        } else {
                            /* 沒有按下左鍵 */
                            mmx = -1;	
                        }
                    }
                } else if (i <= 1) { /* 光標定時器 */
                    ……
                }
            }
        }
    }
    
    • sht->task != 0:圖層的task成員如果是0,代表這個圖層是任務;如果是非0,代表這個圖層是由應用程序產生的
    • 判斷鼠標是否點擊“X”的方法和判斷鼠標是否點擊標題欄是一樣的。
    • 點擊“X”之後的程序結束處理和Shift+F1時的一樣:
      io_cli();	/* 強制結束應用程序時禁止中斷 */
      task_cons->tss.eax = (int) &(task_cons->tss.esp0);
      task_cons->tss.eip = (int) asm_end_app;
      io_sti();
      
      • 將命令行任務的tts.eax賦值tss.esp0,並將eip賦值asm_end_app,即執行asm_end_app。
      • asm_end_app:
        _asm_end_app:
        ;	EAX存放着棧頂指針
                MOV		ESP,[EAX]
                MOV		DWORD [EAX+4],0   ;ss0=0
                POPAD
                RET					; 返回cmd_app
        
        • RET是根據EIP跳轉的,由於事先保存了EIP,所以會調回到cmd_app。
  • make後用VMware運行:

5. 將輸入切換到應用程序窗口(harib21e)

  • 在harib21d中運行walk.hrb應用程序,讓應用程序接收鍵盤輸入,此時處於接收狀態的是命令行窗口,而不是應用程序窗口。

    • 應該讓應用程序窗口處於輸入狀態,然後再進行*的移動。
  • 在這之前,使用Tab鍵在console和task_a之間切換輸入狀態,這時只有兩個圖層。但現在會有三個及以上圖層進行輸入狀態的切換。

    • 按下Tab鍵時,需要判斷切換到了哪個窗口。
    • 規定:按下Tab鍵時,將鍵盤輸入切換到當前輸入窗口的下一層窗口中,噹噹前窗口爲最下層,切換到最上層窗口
  • 之前是使用key_to這個變量判別當前輸入窗口時console還是task_a。顯然現在無法使用key_to變量了。定義一個變量key_win,用於存放當前處於輸入模式的窗口的地址

  • 注意:噹噹前處於輸入狀態的窗口被關閉後,規定讓OS將最上層的圖層變成輸入狀態

  • 修改HariMain:

    void HariMain(void)
    {
        ……
        struct SHEET *sht = 0, *key_win;
        ……
        key_win = sht_win;
        sht_cons->task = task_cons;
        sht_cons->flags |= 0x20;	/* 有光標 */
        ……
        for (;;) {
            ……
            if (fifo32_status(&fifo) == 0) {
                ……
            } else {
                i = fifo32_get(&fifo);
                io_sti();
                if (key_win->flags == 0) {	/* 輸入窗口被關閉 */
                    key_win = shtctl->sheets[shtctl->top - 1]; /*將輸入窗口改成最上層(除鼠標圖層)*/
                    cursor_c = keywin_on(key_win, sht_win, cursor_c); 
                }
                if (256 <= i && i <= 511) { /* 鍵盤數據 */
                    ……
                    if (s[0] != 0) { /* 一般字符 */
                        if (key_win == sht_win) {	/* 發送到任務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(&key_win->task->fifo, s[0] + 256);
                        }
                    }
                    if (i == 256 + 0x0e) {	/* 退格鍵 */
                        if (key_win == sht_win) {	/* 發送到任務A */
                            if (cursor_x > 8) {
                                /* 用空格擦除光標後,光標前移一位 */
                                putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, " ", 1);
                                cursor_x -= 8;
                            }
                        } else {	/* 發送到命令行窗口 */
                            fifo32_put(&key_win->task->fifo, 8 + 256);
                        }
                    }
                    if (i == 256 + 0x1c) {	/* Enter */
                        if (key_win != sht_win) {	/* 發送到命令行窗口 */
                            fifo32_put(&key_win->task->fifo, 10 + 256);
                        }
                    }
                    if (i == 256 + 0x0f) {	/* Tab */
                        cursor_c = keywin_off(key_win, sht_win, cursor_c, cursor_x);
                        j = key_win->height - 1;
                        if (j == 0) {
                            j = shtctl->top - 1;
                        }
                        key_win = shtctl->sheets[j];
                        cursor_c = keywin_on(key_win, sht_win, cursor_c);
                    }
                    ……
                } else if (512 <= i && i <= 767) { /* 鼠標數據 */
                    if (mouse_decode(&mdec, i - 512) != 0) {
                       ……
                        if ((mdec.btn & 0x01) != 0) {
                            /* 按下左鍵 */
                            if (mmx < 0) {
                                /* 如果處於通常模式 */
                                /* 按照從上到下的順序尋找鼠標所指向的圖層 */
                                for (j = shtctl->top - 1; j > 0; j--) {
                                    ……
                                    if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) {
                                        if (sht->buf[y * sht->bxsize + x] != sht->col_inv) {
                                            ……
                                            if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) {
                                                /* 點擊“X”按鈕 */
                                                if ((sht->flags & 0x10) != 0) {		/* 判斷窗口是否是應用程序的窗口 */
                                                    ……
                                                }
                                            }
                                            break;
                                        }
                                    }
                                }
                            } else {
                               ……
                            }
                        } else {
                           ……
                        }
                    }
                } else if (i <= 1) { /* 光標定時器 */
                    ……
                }
            }
        }
    }
    
    • 結構體SHEET的成員task已經不再是原先的含義了,而是用來判斷數據發送對象的FIFO。比如,如果發送給應用程序,那麼應用程序圖層的task指向命令行任務task_console;如果發送給命令行,那麼命令行圖層的task指向命令行任務task_console。注意:如果發送給任務A,那麼可以不讓任務A圖層的task指向任務A,因爲任務A的FIFO是直接從中斷哪裏獲取數據,發送給A的數據可以直接在HariMain中使用(因爲,如果發送給A,當前輸入狀態的是圖層A,那麼直接使用就行了)。
    • 圖層的成員task的含義已經發生了變化,現在已經不能根據task是否爲0來判斷圖層是否是應用程序產生的了。
      • 藉助SHEET結構中的flags標誌,規定,flags的第5位是1:代表是應用程序的圖層;是0代表不是應用程序的圖層
    • 關於光標顯示問題:比如,task_a和console圖層需要光標,而walk應用程序則不需要光標。也就是說,只有命令行窗口需要控制光標ON/OFF,應用程序窗口不需要。
      • 同理,藉助flags也可以進行判斷,規定,flags的第6位是1:代表需要控制光標;是0不需要控制光標
    • SHEET結構體flags詳解:
      • flags的第1位:
        • 0:該圖層未被使用
        • 1:該圖層被使用
      • flags的第5位:
        • 0:該圖層不是應用程序的圖層
        • 1:該圖層是應用程序的圖層
      • flags的第6位:
        • 0:該圖層不需要控制光標
        • 1:該圖層需要控制光標
    • keywin_on和keywin_off函數(bootpack.c中)
      int keywin_off(struct SHEET *key_win, struct SHEET *sht_win, int cur_c, int cur_x)
      {
          change_wtitle8(key_win, 0);
          if (key_win == sht_win) { /*當前處於輸入狀態的圖層是task_a的圖層*/
              cur_c = -1; /* task_a光標OFF */
              boxfill8(sht_win->buf, sht_win->bxsize, COL8_FFFFFF, cur_x, 28, cur_x + 7, 43);
          } else {
              if ((key_win->flags & 0x20) != 0) { /*該圖層需要控制光標,即是命令行圖層*/
                  fifo32_put(&key_win->task->fifo, 3); /* 命令行光標OFF */
              }
          }
          return cur_c;
      }
      
      int keywin_on(struct SHEET *key_win, struct SHEET *sht_win, int cur_c)
      {
          change_wtitle8(key_win, 1);
          if (key_win == sht_win) {
              cur_c = COL8_000000; /* task_a光標顏色 */
          } else {
              if ((key_win->flags & 0x20) != 0) {
                  fifo32_put(&key_win->task->fifo, 2); /* 命令行光標ON */
              }
          }
          return cur_c;
      }
      
      • keywin_on和keywin_off的功能是控制標題欄的顏色和命令行已經task_a的光標顯示。
      • 控制標題欄顏色的功能是由change_wtitle8函數實現的。
      • 編寫change_wtitle8函數(window.c):
        void change_wtitle8(struct SHEET *sht, char act)
        {
            int x, y, xsize = sht->bxsize;
            char c, tc_new, tbc_new, tc_old, tbc_old, *buf = sht->buf;
            if (act != 0) {
                tc_new  = COL8_FFFFFF;
                tbc_new = COL8_000084;
                tc_old  = COL8_C6C6C6;
                tbc_old = COL8_848484;
            } else {
                tc_new  = COL8_C6C6C6;
                tbc_new = COL8_848484;
                tc_old  = COL8_FFFFFF;
                tbc_old = COL8_000084;
            }
            for (y = 3; y <= 20; y++) {
                for (x = 3; x <= xsize - 4; x++) {
                    c = buf[y * xsize + x];
                    if (c == tc_old && x <= xsize - 22) { /*c是字體色*/
                        c = tc_new;
                    } else if (c == tbc_old) { /*c是背景色*/
                        c = tbc_new;
                    }
                    buf[y * xsize + x] = c;
                }
            }
            sheet_refresh(sht, 3, 3, xsize, 21);
            return;
        }
        
        • 其實,make_wtitle8函數也實現了相同的功能,但是change_wtitle8即便不知道窗口的名稱也能改變標題欄的顏色(make是製作,change是改變)。
  • 修改cmd_app:

    int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
    {
        ……
        if (finfo != 0) {
            /* 文件找到 */
            ……
            if (finfo->size >= 36 && strncmp(p + 4, "Hari", 4) == 0 && *p == 0x00) {
                ……
                for (i = 0; i < MAX_SHEETS; i++) {
                    sht = &(shtctl->sheets0[i]);
                    if ((sht->flags & 0x11) == 0x11 && sht->task == task) {
                        /* 找到應用程序遺留的窗口 */
                        sheet_free(sht);	/* 關閉 */
                    }
                }
                memman_free_4k(memman, (int) q, segsiz);
            } else {
                cons_putstr0(cons, ".hrb file format error.\n");
            }
            memman_free_4k(memman, (int) p, finfo->size);
            cons_newline(cons);
            return 1;
        }
        /* 未找到文件 */
        return 0;
    }
    
    • 主要修改的代碼:
      if ((sht->flags & 0x11) == 0x11 && sht->task == task)
      
      • 0x11,即flags的第1位和第5位都是1,那麼代表當前圖層是應用程序產生的圖層且當前圖層正在被使用。
  • 修改hrb_api的5號API(圖層API):

    int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
    {
        ……
        struct TASK *task = task_now();
        ……
        } else if (edx == 5) {
            sht = sheet_alloc(shtctl);
            sht->task = task;
            sht->flags |= 0x10;  /*第5位是1,標誌着這是應用程序的圖層*/
            sheet_setbuf(sht, (char *) ebx + ds_base, esi, edi, eax);
            make_window8((char *) ebx + ds_base, esi, edi, (char *) ecx + ds_base, 0);
            sheet_slide(sht, 100, 50);
            sheet_updown(sht, 3);	
            reg[7] = (int) sht;
        } else if (edx == 6) {
        ……
    }
    
  • make後用VMware運行:

6. 用鼠標切換輸入窗口(harib21f)

  • harib21e實現了用Tab鍵切換輸入窗口。現在使用鼠標切換輸入窗口。

    • 在Windows中,只要鼠標在窗口上點擊一下,那個窗口就會被切換到屏幕的最上方,而且鍵盤輸入也會自動切換到該窗口。
    • 已經實現了點擊窗口該窗口就會切換到最高的圖層,那麼只要實現鼠標點擊窗口,該窗口自動切換到輸入狀態即可。
  • 修改HariMain:

    void HariMain(void)
    {
        ……
        struct SHEET *sht = 0, *key_win;
        ……
        key_win = sht_win;
        sht_cons->task = task_cons;
        sht_cons->flags |= 0x20;	/* 有光標 */
        ……
        for (;;) {
            ……
            if (fifo32_status(&fifo) == 0) {
                ……
            } else {
                ……
                if (256 <= i && i <= 511) { /* 鍵盤數據 */
                    ……
                } else if (512 <= i && i <= 767) { /* 鼠標數據 */
                    if (mouse_decode(&mdec, i - 512) != 0) {
                       ……
                        if ((mdec.btn & 0x01) != 0) {
                            /* 按下左鍵 */
                            if (mmx < 0) {
                                /* 如果處於通常模式 */
                                /* 按照從上到下的順序尋找鼠標所指向的圖層 */
                                for (j = shtctl->top - 1; j > 0; j--) {
                                    ……
                                    if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) {
                                        if (sht->buf[y * sht->bxsize + x] != sht->col_inv) {
                                            sheet_updown(sht, shtctl->top - 1); /*置頂*/
                                            if (sht != key_win) { /*遍歷到的圖層不是當前處於輸入狀態的圖層*/
                                                cursor_c = keywin_off(key_win, sht_win, cursor_c, cursor_x);
                                                key_win = sht; /*切換*/
                                                cursor_c = keywin_on(key_win, sht_win, cursor_c);
                                            }
                                            if (3 <= x && x < sht->bxsize - 3 && 3 <= y && y < 21) {
                                                mmx = mx;	/* 進入窗口移動模式 */
                                                mmy = my;
                                            }
                                            if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) {
                                                /* 點擊“X”按鈕 */
                                                ……
                                            }
                                            break;
                                        }
                                    }
                                }
                            } else {
                               ……
                            }
                        } else {
                           ……
                        }
                    }
                } else if (i <= 1) { /* 光標定時器 */
                    ……
                }
            }
        }
    }
    
    • 鼠標操作切換輸入窗口和鍵盤操作切換輸入窗口差不多。
  • make後VMware運行:

    • 完美實現!

7. 定時器API(harib21g)

  • 讓應用程序也可以使用定時器。因而設計一個定時器APIs:

    • 獲取定時器(alloc)的API:
      • EDX = 16
      • 返回值EAX = 定時器句柄(由OS返回給應用程序)
    • 設置定時器發送數據(init)的API:
      • EDX = 17
      • EBX = 定時器句柄
      • EAX = 數據
    • 定時器時間設置(set)的API:
      • EDX = 18
      • EBX = 定時器句柄
      • EAX = 時間
    • 釋放定時器(free)的API:
      • EDX = 19
      • EBX = 定時器句柄
  • 修改hrb_api:

    int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
    {
        ……
        } else if (edx == 15) {
            for (;;) {
                ……
                if (i >= 256) { /* 鍵盤數據 */
                    reg[7] = i - 256;
                    return 0;
                }
            }
        } else if (edx == 16) {
            reg[7] = (int) timer_alloc();
        } else if (edx == 17) {
            timer_init((struct TIMER *) ebx, &task->fifo, eax + 256);
        } else if (edx == 18) {
            timer_settime((struct TIMER *) ebx, eax);
        } else if (edx == 19) {
            timer_free((struct TIMER *) ebx);
        }
        return 0;
    }
    
    • 之前的時候,在15號API中(api_getkey),通過任務A獲得的數據是字符編碼+256。因此i-256就是數據的字符編碼。現在,通過任務A獲得的數據還包括定時器超時的數據,因此i>=256即可。
    • 在17號API中,定時器的數據是通過eax傳遞進來的,將其+256,這樣產生定時超時中斷髮送給緩衝區的數據就是eax+256,再減去256,就變成了原來的數據。
    • 17號API中,指定了定時器的緩衝區!
  • 編寫可以供C語言調用API的函數:

    _api_alloctimer:	; int api_alloctimer(void);
            MOV		EDX,16
            INT		0x40
            RET
    
    _api_inittimer:		; void api_inittimer(int timer, int data);
            PUSH	EBX
            MOV		EDX,17
            MOV		EBX,[ESP+ 8]		; timer
            MOV		EAX,[ESP+12]		; data
            INT		0x40
            POP		EBX
            RET
    
    _api_settimer:		; void api_settimer(int timer, int time);
            PUSH	EBX
            MOV		EDX,18
            MOV		EBX,[ESP+ 8]		; timer
            MOV		EAX,[ESP+12]		; time
            INT		0x40
            POP		EBX
            RET
    
    _api_freetimer:		; void api_freetimer(int timer);
            PUSH	EBX
            MOV		EDX,19
            MOV		EBX,[ESP+ 8]		; timer
            INT		0x40
            POP		EBX
            RET
    
    • api_alloctimer是調用16號API的函數。分配定時器。
    • api_inittimer是調用17號API的函數。設置當定時器發生超時中斷時,向FIFO緩衝區發送的數據。
    • api_settimer是調用18號API的函數。設置定時器的時間。
    • api_freetimer是調用19號API的函數。刪除定時器。
  • 編寫應用程序noodle.c:

    #include <stdio.h>
    
    int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
    void api_putstrwin(int win, int x, int y, int col, int len, char *str);
    void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
    void api_initmalloc(void);
    char *api_malloc(int size);
    int api_getkey(int mode);
    int api_alloctimer(void);
    void api_inittimer(int timer, int data);
    void api_settimer(int timer, int time);
    void api_end(void);
    
    void HariMain(void)
    {
        char *buf, s[12];
        int win, timer, sec = 0, min = 0, hou = 0;
        api_initmalloc();
        buf = api_malloc(150 * 50);
        win = api_openwin(buf, 150, 50, -1, "noodle");
        timer = api_alloctimer();
        api_inittimer(timer, 128);
        for (;;) {
            sprintf(s, "%5d:%02d:%02d", hou, min, sec);
            api_boxfilwin(win, 28, 27, 115, 41, 7 /* 白色 */);
            api_putstrwin(win, 28, 27, 0 /* 黑色 */, 11, s);
            api_settimer(timer, 100);	/* 1s */
            if (api_getkey(1) != 128) { /*不是定時器超時發送來的數據,就退出*/
                break;
            }
            sec++;
            if (sec == 60) {
                sec = 0;
                min++;
                if (min == 60) {
                    min = 0;
                    hou++;
                }
            }
        }
        api_end();
    }
    
    • 如果從鍵盤獲取的數據不是128,那麼一定是用戶按了回車鍵或者其他的鍵,這時應用程序退出。
  • make後用VMware運行:

    • 時間過得真快啊,轉眼間四分鐘過去了(笑)。

8. 取消定時器(harib21h)

  • 在harib21g中,當結束noodle.hrb後(不論是按Enter或者鼠標點擊“X”),命令行會出現一個神祕的字符(不是字符C哦)。這是我們不希望出現的。

    • 應用程序設置了一個1s定時器,每個1s就會產生一次超時中斷並向緩衝區發送事先設置的數據。如果應用程序結束了,定時器的數據就會發送到命令行窗口。
    • 查看console_task中的代碼,發現只要數據介於256和511之間,如果數據不是退格鍵和Enter,那麼就往命令行上寫入。
    • 在harib21g中,數據是128,因此當應用程序結束以後,定時器產生的數據會發送到命令行窗口,命令行接收到128+256,剛好介於範圍內,所以就會出現128這個什麼字符。
    • 解決方法有2種:
      • 其一,數據設成大於512的數字(治標不治本)。
      • 其二,取消定時器(治標治本)。
  • 編寫用於取消定時器的函數timer_cancel(timer.c):

    int timer_cancel(struct TIMER *timer)
    {
        int e;
        struct TIMER *t;
        e = io_load_eflags();
        io_cli();	/* 在設置定時器的過程中禁止中斷 */
        if (timer->flags == TIMER_FLAGS_USING) {	/* 只有定時器正在使用才能被取消 */
            if (timer == timerctl.t0) {
                /* 當要取消的定時器是第一個定時器 */
                t = timer->next;
                timerctl.t0 = t;
                timerctl.next = t->timeout;
            } else {
                /* 非第一個定時器的取消處理 */
                /* 找到timer前的定時器 */
                t = timerctl.t0;
                for (;;) {
                    if (t->next == timer) {
                        break;
                    }
                    t = t->next;
                }
                t->next = timer->next; /* 更新next */
            }
            timer->flags = TIMER_FLAGS_ALLOC;
            io_store_eflags(e);
            return 1;	/* 取消處理成功 */
        }
        io_store_eflags(e);
        return 0; /* 取消處理失敗,即定時器不需要取消 */
    }
    
    • 回顧:timer們的管理使用了鏈表數據結構+哨兵。
  • 修改TIMER結構體:

    struct TIMER {
        struct TIMER *next;
        unsigned int timeout;
        char flags, flags2;
        struct FIFO32 *fifo;
        int data;
    };
    
    • 新增了flags2變量,用於標記該定時器是否需要在應用程序結束後自動取消
      • 這樣做是爲了不取消光標定時器。
      • flags2 =
        • 0:不自動取消
        • 1:自動取消
      • 通常,flags2 = 0。
  • 修改timer_alloc函數:

    struct TIMER *timer_alloc(void)
    {
        int i;
        for (i = 0; i < MAX_TIMER; i++) {
            if (timerctl.timers0[i].flags == 0) {
                timerctl.timers0[i].flags = TIMER_FLAGS_ALLOC;
                timerctl.timers0[i].flags2 = 0; /*通常情況下,定時器不自動取消*/
                return &timerctl.timers0[i];
            }
        }
        return 0; 
    }
    
  • 將應用程序申請的定時器的flags2標誌設置爲1,修改hrb_api中的16號API:

    int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
    {
        ……
        } else if (edx == 16) {
            reg[7] = (int) timer_alloc();
            ((struct TIMER *) reg[7])->flags2 = 1;	/* 自動取消 */
        } else if (edx == 17) {
        ……
    }
    
    • reg[7]是eax,是返回值,是定時器句柄。
  • 修改cmd_app,應用程序結束後取消不需要的定時器:

    int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
    {
        ……
        if (finfo != 0) {
            ……
            if (finfo->size >= 36 && strncmp(p + 4, "Hari", 4) == 0 && *p == 0x00) {
                ……
                timer_cancelall(&task->fifo); /*取消不需要的定時器*/
                memman_free_4k(memman, (int) q, segsiz);
            } else {
                cons_putstr0(cons, ".hrb file format error.\n");
            }
            memman_free_4k(memman, (int) p, finfo->size);
            cons_newline(cons);
            return 1;
        }
        return 0;
    }
    
    • 編寫timer_cancelall函數(timer.c中):
      void timer_cancelall(struct FIFO32 *fifo)
      {
          int e, i;
          struct TIMER *t;
          e = io_load_eflags();
          io_cli();	/* 中斷禁止 */
          for (i = 0; i < MAX_TIMER; i++) {
              t = &timerctl.timers0[i];
              if (t->flags != 0 && t->flags2 != 0 && t->fifo == fifo) {
                  timer_cancel(t);
                  timer_free(t);
              }
          }
          io_store_eflags(e);
          return;
      }
      
      • 參數是緩衝區指針,因爲每一個定時器都有緩衝區,根據傳進來的參數,判斷定時器是否屬於該緩衝區。如果是,再判斷定時是否正在運行和flags2標誌。
      • 如果是,取消定時器,並釋放之。
  • make後用VMware運行:

    • 重新運行noodle.hrb並關閉之:
      • 沒出現神祕字符!

9. 寫在14:36

  • 現在是2020.4.30 14:36,結束了第24天的內容,超過了500頁。
  • 現在外面天氣很好,陽光明媚,萬里無雲。
  • 寫文檔寫的右手中指關節痛。可能壓力太大,今天食慾不振,中午只吃了一個小包子。
  • 現在,爲了讓自己精神貫注,已經開始藉助茶葉了。
  • 現在開始第25天的內容吧,明天是五一勞動節,還是不給自己放假了吧。
  • 興趣加專注,方能不斷進取!
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章