# ncurses 教學 [TOC] ## 什麼是 ncurses ncurses(new curses)是一個程式函式庫,它提供了API 讓程式設計師編寫獨立於終端的基於文字的使用者介面 (TUI,**t**ext-based **u**ser **i**nterfaces) ## 安裝 - Manual 從 https://ftp.gnu.org/gnu/ncurses/ 下載,並照著 README 和 INSTALL 檔案中的步驟安裝 - Ubuntu/Debian ```bash! sudo apt install libncurses5-dev libncursesw5-dev ``` - Fedora/RHEL ```bash! sudo dnf install ncurses-devel ``` - Arch ```bash! sudo pacman -S ncurses ``` ## 開始使用 ### 引入 ncurses 使用 ncurses 時要引用 `ncurses.h` 標頭檔 ```c! #include <ncurses> ``` ### 編譯 編譯時要加上 `-lncurses` 使編譯器連結 ncurses 函式庫 ### Hello World 這是一個使用 ncurses 並顯示 Hello World! 的範例程式,之後會詳細說明函式的用法。 ```c= // helloworld.c #include <ncurses.h> int main(){ initscr(); printw("Hello World!"); refresh() getch(); endwin(); return 0; } ``` 編譯指令 ```bash! gcc -o helloworld helloworld.c -lncurses ``` 執行畫面 ![image](https://hackmd.io/_uploads/Sk1FPcnxgl.png) ## 初始設定 一開始當然要先進行一些初始設定,這是讓你從終端邁入 TUI 的第一步 ### 初始化 curses 視窗 - `initscr()` 初始化並開啟 curses 視窗 ### 結束 curses 視窗 - `endwin()` 結束 curses 視窗並回到終端 ### 禁用輸入緩衝 通常,終端會緩衝用戶輸入的字符,直到遇到 tab 或 enter 為止。 但是我們希望開發能夠在使用者輸入後直接送給程式 - `cbreak()` 關閉行緩衝,鍵盤輸入即時送出 - `raw()` 更嚴格的即時輸入模式,連 Ctrl-C 等中斷鍵也會被程式接收。 ### 鍵盤輸入顯示 控制是否將鍵盤輸入的字符顯示到終端。 - `echo()` 開啟顯示 - `noecho()` 關閉顯示 ### 特殊鍵讀取 - `keypad(stdscr, TRUE)` 啟用讀取特殊鍵,像是 F1 ~ F12、方向鍵 ## 輸出 開啟 curses 視窗會蓋住整個終端,因此使用 printf 會看不到 ### 輸出函式 - `printw(const char *fmt, ...)` 使用方法和 printf 一樣,會將文字輸出到 curses 視窗上 - `addch(const chtype ch)` 輸出單一字元 :::info 可以發現,addch 的參數型態是 chtype 不是 char,因為它可以設定一些屬型,例如輸出一個體加底線的字元可以這樣 ```c! addch(ch | A_BOLD | A_UNDERLINE); ``` 詳細的屬性會在後面提到 ::: - `addstr(const char * str)` 輸出一個字串 ### 清空內容 - `clear()` 清空螢幕上所有內容 ### 刷新 使用前面的輸出和清空後都要使用 `refresh()` ,螢幕上的內容才會刷新 ### 移動游標 - `move(int y, int x)` 移動游標到 x 行第 y 個字 ( x 和 y 從0開始 ) 在前面介紹的輸出函式前加上 mv 就可以直接移動並輸出,例如 ```c! move(10, 10); addch('A'); ``` 可以用 `mvaddch()` 一個函式代替 ```c! mvaddch(10, 10, 'A'); ``` ### 屬性 ncurses 有 attr 屬性,用來為輸出的文字添加特殊效果 ``` A_NORMAL 正常顯示 A_BOLD 粗體或高亮 A_DIM 暗淡(比正常文字亮度低) A_UNDERLINE 底線 A_REVERSE 反相顯示(黑底白字) A_STANDOUT 突出顯示(通常是反相文字) A_BLINK 閃爍 A_INVIS 不可見或空白 A_ALTCHARSET 使用替代字元集 A_PROTECT 文字保護,防止文字被覆蓋 ``` - `attron(attr)` 開啟指定屬性 - `attroff(attr)` 關閉指定屬性 - `attrset(attr)` 設置屬性並覆蓋當前視窗的所有屬性 如果想要同時使用多個屬性,可以使用 `|` (or) 來隔開,例如同時開啟粗體和底線 ```c~ attron(A_BOLD | A_UNDERLINE); ``` ### 顏色 #### 初始化顏色功能 - `bool has_colors()` 判斷終端是否支持顏色功能 - `start_color()` 啟用顏色功能 #### 改變輸出顏色 1. 建立顏色對 `init_pair(short pair, short f, short b)` `pair` 是顏色對的編號(0 到 COLOR_PAIRS - 1) `f` 是前景(文字顏色)顏色,使用顏色編號(0 到 COLOR - 1) `b` 是背景顏色,同樣用顏色編號 有一些預定義的顏色巨集可以使用 ``` COLOR_BLACK 0 COLOR_RED 1 COLOR_GREEN 2 COLOR_YELLOW 3 COLOR_BLUE 4 COLOR_MAGENTA 5 COLOR_CYAN 6 COLOR_WHITE 7 ``` 2. 套用顏色對 把 `COLOR_PAIR(n)` 當作屬性套用,n 是剛剛的顏色對編號 #### 範例 ```c= #include <ncurses.h> int main(){ initscr(); if (has_colors() == FALSE){ endwin(); printf("Your terminal does not support color\n"); return 1; } start_color(); init_pair(1, COLOR_RED, COLOR_BLUE); attron(COLOR_PAIR(1)); printw("This text is red and blue background\n"); attroff(COLOR_PAIR(1)); getch(); endwin(); return 0; } ``` ![image](https://hackmd.io/_uploads/SyNnA03llg.png) #### 更改顏色定義 如果不喜歡預定義的顏色,你可以使用 `init_color(short color, short r, short g, short b)` color 是顏色編號,將改顏色編號改成指定的rgb,rgb的值最小為0,最大為1000 ## 輸入 前面介紹了輸出,當然也會有輸入 ### 鍵盤輸入 - `scanw(const char *fmt, ...)` 使用方法和 scanf 相同 - `int getch()` 等待用戶按下一個鍵,並回傳該鍵的鍵值 如果輸入的是普通字元,那就會回傳該字元的ascii值;否則會返回和可以與 ncurses.h 中定義的巨集相匹配的數字 例如按下「向下鍵」時輸出 "press down" ```c! if(getch == KEY_DOWN){ printw("press down"); } ``` :::warning 要使用特殊鍵記得先啟用特殊鍵讀取 `keypad(stdscr, TRUE)` ::: - `getstr(char *str)` 取得輸入字串到 str ### 滑鼠輸入 #### 設定滑鼠事件類型 要使用滑鼠輸入,需要先啟用並指定接收類型 `mousemask(mmask_t new-mask, mmask_t *old-mask)` 在 new-mask 填入滑鼠事件類型,old-mask 可以填入 NULL 滑鼠事件類型: ``` BUTTON1_PRESSED 滑鼠按鈕 1 按下 BUTTON1_RELEASED 滑鼠按鈕 1 放開 BUTTON1_CLICKED 滑鼠按鈕 1 單擊 BUTTON1_DOUBLE_CLICKED 滑鼠按鈕 1 雙擊 BUTTON1_TRIPLE_CLICKED 滑鼠按鈕 1 三擊 BUTTON2_PRESSED 滑鼠按鈕 2 按下 BUTTON2_RELEASED 滑鼠按鈕 2 放開 BUTTON2_CLICKED 滑鼠按鈕 2 單擊 BUTTON2_DOUBLE_CLICKED 滑鼠按鈕 2 雙擊 BUTTON2_TRIPLE_CLICKED 滑鼠按鈕 2 三擊 BUTTON3_PRESSED 滑鼠按鈕 3 按下 BUTTON3_RELEASED 滑鼠按鈕 3 放開 BUTTON3_CLICKED 滑鼠按鈕 3 單擊 BUTTON3_DOUBLE_CLICKED 滑鼠按鈕 3 雙擊 BUTTON3_TRIPLE_CLICKED 滑鼠按鈕 3 三擊 BUTTON4_PRESSED 滑鼠按鈕 4 按下 BUTTON4_RELEASED 滑鼠按鈕 4 放開 BUTTON4_ CLICKED 滑鼠按鈕 4 單擊 BUTTON4_DOUBLE_CLICKED 滑鼠按鈕 4 雙擊 BUTTON4_TRIPLE_CLICKED 滑鼠按鈕 4 三擊 BUTTON_SHIFT shift鍵在滑鼠狀態變更時按下 BUTTON_CTRL control鍵在滑鼠狀態變更時按下 BUTTON_ALT alt鍵在滑鼠狀態變更時按下 ALL_MOUSE_EVENTS 接收所有滑鼠狀態 REPORT_MOUSE_POSITION 回報滑鼠位置 ``` 同樣可以使用 `|` 隔開兩個類型,如接收所有滑鼠事件及回報滑鼠位置 ```c! mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL); ``` #### 取得滑鼠事件 1. 先宣告一個滑鼠事件結構 `MEVENT` 的變數 2. 使用 `getch()` 等待並偵測動作 :::warning 滑鼠為特殊鍵記得先啟用特殊鍵讀取 `keypad(stdscr, TRUE)` ::: 3. 如果鍵值是滑鼠,再取得滑鼠事件 `getmouse(&event)` 4. 接著利用 `event.bstate` 判斷事件類型 #### 範例 按下滑鼠鍵1時顯示按下的位置,直到按下鍵盤 Q 鍵退出 ```c= #include <ncurses.h> int main(){ initscr(); keypad(stdscr, TRUE); mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, NULL); MEVENT event; int ch = 0; while(1){ ch = getch(); if(ch == KEY_MOUSE){ if(getmouse(&event) == OK){ if(event.bstate & BUTTON1_CLICKED){ printw("mouse left button clicked at (%d, %d)\n", event.x, event.y); refresh(); } } } else if(ch == 'q'){ break; } } endwin(); return 0; } ``` ## 視窗 視窗是 ncurses 中最重要的一部分。一開始我們使用 initscr() 建立了一個標準視窗 (stdscr),所有的函式默認執行在這個視窗上。有時候我們需要更多視窗來顯示更多資訊或提高效率。 ### 建立新視窗 - `WINDOW *newwin(int nlines, int ncols, int begin_y, int begin_x)` 建立一個新視窗,並返回一個 WINDOW 類型指標 ### 刪除視窗 - `int delwin(WINDOW * win)` 刪除視窗並釋放記憶體 :::warning 刪除視窗時螢幕上的內容不會消失,因此刪除前先使用 `wclear()` 和 `wrefresh()` 清理顯示的內容 ::: ### 視窗邊框 - `box(WINDOW *win, chtype verch, chtype horch)` 繪製簡單邊框 `verch` 是左右邊框字元,0為預設邊框 `horch` 是上下邊框字元,0為預設邊框 - `wborder(WINDOW *win, chtype ls, chtype rs, chtype ts, chtype bs, chtype tl, chtype tr, chtype bl, chtype br)` 自訂邊框的 8 個字元 `ls` 左邊框,`rs` 右邊框 `ts` 上邊框,`bs` 下邊框 `tl` 左上角,`tr` 右上角 `bl` 左下角,`br` 右下角 ### 視窗函式 要在上窗中使用前面介紹的輸入輸出等函式很簡單,只需要再函式名稱前方加上 w,並且第一個參數代入視窗指標。 ### 子視窗 我們可以在視窗中建立與該視窗共用緩衝區的子視窗 - `WINDOW *subwin(WINDOW *orig, int nlines, int ncols, int begin_y, int begin_x)` 建立與父視窗共用緩衝區的子視窗,座標為「螢幕絕對座標」 - `WINDOW *derwin(WINDOW *orig, int nlines, int ncols, int begin_y, int begin_x)` 建立與父視窗共用緩衝區的子視窗,座標為「父視窗相對座標」 ## reference - [NCURSES Programming HOWTO](https://tldp.org/HOWTO/NCURSES-Programming-HOWTO/index.html) - [(超详细) ncurses 库使用介绍: 实现终端 GUI](https://www.cnblogs.com/VeniVidiVici/p/17318232.html)