第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時,當前模式不是窗口移動模式。
- mmx和mmy中的mm是
-
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:該圖層需要控制光標
- flags的第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 = 定時器句柄
- 獲取定時器(alloc)的API:
-
修改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中,指定了定時器的緩衝區!
- 之前的時候,在15號API中(api_getkey),通過任務A獲得的數據是
-
編寫可以供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。
- 新增了flags2變量,用於標記該定時器是否需要在應用程序結束後自動取消。
-
修改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標誌。
- 如果是,取消定時器,並釋放之。
- 編寫timer_cancelall函數(timer.c中):
-
make
後用VMware運行:- 重新運行noodle.hrb並關閉之:
- 沒出現神祕字符!
- 重新運行noodle.hrb並關閉之:
9. 寫在14:36
- 現在是2020.4.30 14:36,結束了第24天的內容,超過了500頁。
- 現在外面天氣很好,陽光明媚,萬里無雲。
- 寫文檔寫的右手中指關節痛。可能壓力太大,今天食慾不振,中午只吃了一個小包子。
- 現在,爲了讓自己精神貫注,已經開始藉助茶葉了。
- 現在開始第25天的內容吧,明天是五一勞動節,還是不給自己放假了吧。
- 興趣加專注,方能不斷進取!