【问题标题】:Input bar at console bottom in CC中控制台底部的输入栏
【发布时间】:2012-01-29 19:58:17
【问题描述】:

窗口底部

一些应用程序,如 vim、mutt、aptitude 包含

  • 用于输出的顶部窗口部分和
  • 供用户输入或状态显示的底部。

(假设有一个子进程要输出,另一个子进程接受用户输入。目的是允许在您输入输入或查看状态的同时更新输出。)

Actions  Undo  Package  Resolver  Search  Options  Views  Help
C-T: Menu  ?: Help  q: Quit  u: Update  g: Download/Install/Remove Pkgs



                                                                            |
                                                                            |
                                                                            |
                                                                            |
                                                                            |
                            ┌─────────────┐                                 |
                            │Loading cache│                                 |
                            └─────────────┘                                 |
                                                                            |
                                                                            |
                                                                            |
                                                                            |
                                                                            |
                                                                            |    
                                                                            |
--------------------------------------------------------------------------- |
Initialising package states                                            100% |

+-------------------------------------------------------+
| some output here                                      |
|                                                       |
|                                                       |
|                                                       |
|                                                       |
|                                                       |
|-------------------------------------------------------+
|:input here                                            |
+-------------------------------------------------------+

Ncurses tutorial 没有提到这显然是可能的。

在 StackOverflow 或网络搜索引擎上查询“c print to {window,screen,terminal,console} bottom”没有帮助。

这可以在 C 中以编程方式完成吗?

丢弃输入

虽然下面的一些解决方案可以将字符移动到给定位置,但存在的问题是可能需要丢弃用户输入而不是将其留在屏幕上。就像vim 的情况一样,输入“:w”并按 Enter 不会在屏幕上留下“:w”。

更新。可以在这里找到:How to delete text after getstr() c++ ncurses

窗口焦点 - 问题的未解决部分

当您在窗口底部键入输入并且顶部的文本发生更改时,我们看到将焦点移回底部的问题。截至 12 月 29 日的解决方案中不存在这一点。


更新 1. 只是尝试

  • 记住之前的光标位置,然后
  • 显示输出,然后
  • 恢复位置

不是一个简单的解决方案:由于这些是不同的进程,检索光标位置的尝试不会影响其他进程执行期间发生的更改。

例如,如果父母接受输入,那么孩子不知道光标位置是如何变化的,并且在控制台的另一部分执行一行输出后无法恢复光标位置。

实现这将涉及一些进程间通信,如果有其他解决方案,它们可能会更可取。

相关

【问题讨论】:

标签: c unix input console command-line-interface


【解决方案1】:

使用标准库,没有办法做到这一点;正如您已经建议的那样,使用 ncurses 很容易;我认为this tutorial 解释得很好。

【讨论】:

  • @gry:C 应该没问题。用gcc 代替g++,用0 代替false 等等。
  • 我同意curses 可能是要走的路,尽管在解决阅读过程和显示过程之间的通信时会有一些有趣的问题。
  • 这个问题可能需要多个窗口,本教程不详述。
  • @DietrichEpp 您也可以在 C 中使用 false,只需包含 <stdbool.h>
  • @jørgensen:以防万一,您可能还必须使用 -std=c99,因为 stdbool.h 是 C99 标头。
【解决方案2】:

这个答案提出了一种稍微不同的方法,但它避免了使用当前方法引入的许多复杂性。我希望可以实现这些简化。

使用 termio 关闭规范模式。

示例代码(here) 将向您展示如何设置这样的输入循环。此示例“轮询和睡眠”可能意味着延迟,除非您减少睡眠。另外,我认为您可以使用 termio 设置超时(稍等片刻并返回“无输入”,如果输入更早,请立即给我输入)。但是如果你真的要监控另一个进程,轮询可能是更灵活的选择。你真的可以每秒轮询 30 次,并忍受它将导致 .00001% 的处理器命中率,但你会喜欢这种简单性和错误它提供了预防。

避免像瘟疫一样的多线程和进程

如果您尝试解决的唯一问题是 getch() 阻塞的事实,则不需要使用 2 个进程/线程。如果无法阻止输入功能阻塞,则需要这样做。 “规范”(基于规则)意味着各种方便的“规则”都有效,例如,“在按下 ENTER 之前不要向程序提供输入”。对于一个全屏控制台应用程序,您希望关闭所有规则并自己做所有事情。

让主线程负责窗口...

...然后您可以使用ansi escape csi codes 将光标定位回您想要的位置。警告:您不能写入屏幕右下角的框。一切都会滚动。

在 MS windows 编程中有一件烦人的事情,只有创建窗口的线程才能安全地更新它。这其实是有原因的。无论您是在谈论控制台还是窗口系统。迟早,如果您有多个线程/处理访问一个输出设备,您将中断转义序列(或必须制作额外的代码来管理这是一件坏事),为输出“端口”而战,等等. 你需要一个线程来管理输出。

如果您真的关心其他线程或进程在做什么,只需在您的主控制台管理循环中检查即可。例如,您有另一个进程,您只想报告其进度,从您的程序启动它并捕获其标准输出并查看它;另一个线程,只需锁定共享的内容,您可以在轮询例程中检查。哎呀,如果它只是一个字节并且仅用于状态,甚至不要锁定该死的东西。你也可以扔几个 GOTO 来展示你的个性:-)

警告如果您手动弄乱 termio,我不知道 ncurses 会与您配合得很好吗?我猜它想自己做。从来没有尝试过混合。如果您的应用程序很简单,您可以在没有帮助的情况下单独使用它,特别是如果您可以努力让 ncurses 做您想做的事。我不是您提到的那些应用程序的专家,但我敢打赌,它们对所有事情都进行了微观管理。

【讨论】:

    【解决方案3】:

    几周前我在编写一个在终端中运行的 IRC 客户端时遇到了类似的问题。我使用 Windows conio 库编写了它,但我相当确定这应该适用于诅咒。这个想法是控制台输出由单个线程处理,控制台输入在单独的线程中处理。基本上,您所需要的只是一个循环,它将 getch() 的返回推送到在程序期间运行的互斥 FIFO。在显示线程中,您可以将按键从 FIFO 中弹出并按您喜欢的方式处理它们。您不能使用像 fgets() 这样的标准函数,但它是解决问题的非常可靠的方法。我可以根据要求提供完整的(混乱的)来源。

    编辑:好的,这是来自 FIFO 推送的相关代码:

    bool keyqueuewriting = false;
    std::deque<int> keyqueue;
    void grabkey( void* in )
    {
        int t;
        while( true ){
            t = getch();
            #ifdef _WIN32
            if( t == 224 || t == 0 )
            {
                t += getch() << 8;
            }
            #else
                int off = 8;
                if( t == 27 ){
                    int e = getch();
                    t += e << off;
                    off += 8;
                    while( e ==91 || (e >= '0' && e <= '9') || e == ';' )
                    {
                        e = getch();
                        t += e << off;
                        off += 8;
                    }
                }
            #endif
            while( keyqueuewriting ){}
            keyqueuewriting = true;
            keyqueue.push_back( t );
            keyqueuewriting = false;
        }
    }
    

    及处理:

    while( keyqueuewriting ){}
    keyqueuewriting = true;
    while( keyqueue.size() > 0 )
    {
        shouldsleep = false;
        int t = keyqueue.front();
        keyqueue.pop_front();
        switch( t )
        {
            case K_BACKSPACE:
                if( pos > 0 ){
                    for( int i = pos-1; input[i] != 0; i++ ){input[i] = input[i+1];}
                    movecursorback( 1 );
                    pos -= 1;
    
                    } break;
            case K_LEFT: if( pos > 0 ){ movecursorback( 1 ); pos -= 1; } break;
            case K_RIGHT: if( input[pos] != 0 ) {movecursorforward( 1 ); pos += 1;} break;
            case K_HOME: { gotoxy(0,SCREENHIG-1); pos = 0; } break;
            case K_END: { int a = strlen( input ); /*gotoxy( 79,39 );*/ pos = a;} break;
            case 3: exit(3); break;
    
            default: if( t >= 0x20 && t < 0x80 ){
                    int a = strlen( input );
                    if( a > 998 )
                        a = 998;
                    int deadcode = 1;
                    input[999] = 0;
                    for( int i = a+1; i > pos; i-- ){input[i] = input[i-1];}
                    input[ pos ] = t;
                    movecursorforward( 1 );
                    pos++;
                    } break;
        }
        change = bufpos[curroom] - bufprev;
        if( pos > 998 ) pos = 998;
        if( pos - mescroll < 1 ) {mescroll += (pos-mescroll-1); gotoxy( pos-mescroll, SCREENHIG-1 );}
        if( pos - mescroll > 78 ) {mescroll += (pos-mescroll-78); gotoxy( pos-mescroll, SCREENHIG-1 );}
        if( mescroll < 0 ) {mescroll = 0; gotoxy( 0, SCREENHIG-1 ); }
        savexy();
        gotoxy( 0, SCREENHIG-1 );
        char y = (input+mescroll)[79];
        (input+mescroll)[79] = 0;
        printf( "%s   ", input+mescroll );
        (input+mescroll)[79] = y;
    
        returntosaved();
        change2 = change;
        bufprev = bufpos[curroom];
    }
    keyqueuewriting = false;
    

    是的,它使用 std::deque。这应该是唯一的 C++ 特定的东西。只需将其替换为与 C 兼容的 FIFO。

    整个客户端可以是found here. 是的,它可以在 Linux 中编译,但它不起作用。在我开始工作之前,我从来没有费心弄清楚应该如何使用 ncurses。

    【讨论】:

      【解决方案4】:

      使用 ANSI 转义序列可以控制光标的位置:

      void gotoxy(int x, int y) {
          printf("\033[%d;%dH",x,y);
      }
      

      因此,一旦您确定了terminal height and width,您就可以使用该功能将光标定位到您喜欢的任何位置并打印内容。

      【讨论】:

      • @gry: 看here
      • 您假设 ANSI 转义序列将在任何正在使用的终端上正常工作,而无需任何启动。 curses/ncurses 几乎肯定会更容易
      • @mpez:是的。我建议的 ANSI 转义序列适用于 linux、mac 和 windows 终端上的 xterm。如果有人可以提供一个链接来记录他们不会工作的地方,那就太好了。
      • @holygeek Betcha 转义序列在 xterm tektronix 窗口中不起作用,仅在 vt100 窗口中起作用。对于更可能的情况,它们不适用于 Windows 命令行和 telnet。 curses/ncurses 数据库将显示哪些终端采用哪些序列。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-20
      • 2014-06-19
      • 2011-01-15
      • 1970-01-01
      • 2019-02-15
      相关资源
      最近更新 更多