簡體   English   中英

在沒有 ncurses 的情況下用 C/C++ 編寫“真正的”交互式終端程序,如 vim、htop 等

[英]Writing a “real” interactive terminal program like vim, htop, … in C/C++ without ncurses

不,我不想使用ncurses ,因為我想了解終端的工作原理並在自己的編程中獲得樂趣。 :) 它不必是可移植的,它只能在基於 linux xterm 的終端仿真器上工作。

我想做的是編寫一個像 htop 和 vim 這樣的交互式終端應用程序。 我的意思不是輸出看起來像盒子或設置顏色的字符,這是微不足道的; 也使內容適合窗口大小。 我需要的是

  1. 如何獲得鼠標交互,例如單擊字符並滾動鼠標滾輪(當鼠標位於特定字符上時)以實現滾動[編輯:當然是在終端模擬器中],以及

  2. 如何完全保存和恢復父進程的輸出並將我的打印與其輸出分開,所以在離開我的應用程序之后,除了我在 shell 中輸入的命令之外,應該在那里,就像運行 htop 並再次退出時一樣:什么都不可見從這個應用程序了。

我真的不想使用 ncurses。 但是當然,如​​果你知道ncurses的哪個部分負責這些任務,歡迎你告訴我在源代碼中我可以找到它,所以我會研究它。

為了操作終端,您必須使用控制序列 不幸的是,這些代碼取決於您使用的特定終端。 這就是terminfo (以前稱為termcap )首先存在的原因。

您不會說是否要使用 terminfo 。 所以:

  • 如果您將使用 terminfo,它將為您的終端支持的每個操作提供正確的控制序列。
  • 如果您不使用 terminfo... 好吧,您必須手動編碼您想要支持的每種終端類型中的每個操作。

由於您希望將其用於學習目的,我將在第二部分詳細說明。

您可以從環境變量$TERM發現您正在使用的終端類型。 在 linux 中,最常見的是用於終端仿真器(XTerm、gnome-terminal、konsole)的xterm和用於虛擬終端(X 未運行時的那些)的linux

您可以使用命令tput輕松發現控制序列。 但是當tput在控制台上打印它們時,它們會立即應用,所以如果你想真正看到它們,請使用:

$ TERM=xterm tput clear | hd
00000000  1b 5b 48 1b 5b 32 4a                              |.[H.[2J|

$ TERM=linux tput clear | hd
00000000  1b 5b 48 1b 5b 4a                                 |.[H.[J|

也就是說,要在xterm清除屏幕,您必須在xterm中輸出ESC [ H ESC [ 2J ,而在 linux 終端中輸出ESC [ H ESC [ J

關於您詢問的特定命令,您應該仔細閱讀man 5 terminfo 那里有很多信息。

雖然這是一個有點老的問題,但我想我應該分享一個簡短的例子來說明如何在不使用 ncurses 的情況下做到這一點,這並不難,但我相信它不會那么便攜。

此代碼將 stdin 設置為原始模式,切換到備用緩沖區屏幕(在啟動之前保存終端的狀態),啟用鼠標跟蹤並在用戶單擊某處時打印按鈕和坐標。 使用Ctrl + C退出后,程序將恢復終端配置。

#include <stdio.h>
#include <unistd.h>
#include <termios.h>

int main (void)
{
    unsigned char buff [6];
    unsigned int x, y, btn;
    struct termios original, raw;

    // Save original serial communication configuration for stdin
    tcgetattr( STDIN_FILENO, &original);

    // Put stdin in raw mode so keys get through directly without
    // requiring pressing enter.
    cfmakeraw (&raw);
    tcsetattr (STDIN_FILENO, TCSANOW, &raw);

    // Switch to the alternate buffer screen
    write (STDOUT_FILENO, "\e[?47h", 6);

    // Enable mouse tracking
    write (STDOUT_FILENO, "\e[?9h", 5);
    while (1) {
        read (STDIN_FILENO, &buff, 1);
        if (buff[0] == 3) {
            // User pressd Ctr+C
            break;
        } else if (buff[0] == '\x1B') {
            // We assume all escape sequences received 
            // are mouse coordinates
            read (STDIN_FILENO, &buff, 5);
            btn = buff[2] - 32;
            x = buff[3] - 32;
            y = buff[4] - 32;
            printf ("button:%u\n\rx:%u\n\ry:%u\n\n\r", btn, x, y);
        }
    }

    // Revert the terminal back to its original state
    write (STDOUT_FILENO, "\e[?9l", 5);
    write (STDOUT_FILENO, "\e[?47l", 6);
    tcsetattr (STDIN_FILENO, TCSANOW, &original);
    return 0;
}

注意:對於超過 255 列的終端,這將無法正常工作。

我發現的轉義序列的最佳參考是這個這個

我有點困惑。 你說的是“終端應用程序”,比如 vim; 終端應用程序不獲取鼠標事件,也不響應鼠標。

如果您談論的是在xterm中運行的真實終端應用程序,那么需要注意的重要一點是,許多可移植性問題都與終端有關,而不是操作系統。 通過發送不同的轉義序列來控制終端。 哪些做什么取決於終端; ANSI 轉義碼現在相當普遍,但是,請參閱http://en.wikipedia.org/wiki/ANSI_escape_code 例如,這些通常被xterm理解。

您可能必須在開始和結束時輸出額外的序列才能進入和離開“全屏”模式; 這對於xterm是必要的。

最后,您必須在輸入/輸出級別做一些特殊的事情,以確保您的輸出驅動程序不會添加任何字符(例如將簡單的 LF 轉換為 CRLF),並確保輸入不會回顯,是透明的,並立即返回。 在 Linux 下,這是使用ioctl完成的。 (同樣,完成后不要忘記恢復它。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM