【问题标题】:Move the cursor in a C program在 C 程序中移动光标
【发布时间】:2016-01-06 15:42:43
【问题描述】:

我想在 C 程序中前后移动光标。我正在循环读取整行,但我希望如果按下光标键,屏幕上的光标会改变位置,而不会阻塞循环。我试过getwch(),但它会阻止调用者,直到它被按下。我正在寻找的是类似于 bash 提示符的行为。我正在阅读与此类似的代码:

while (TRUE) {
   printf("%s", PROMPT);
   fgets(input, 1024, stdin);
   do_something(input);
}

我正在尝试让上面的函数像 readline(PROMPT) 一样在 readline.h 库中工作

【问题讨论】:

  • 如果没有 ncurses 库之类的东西就无法做到这一点
  • 听起来你需要readline,这也是bash使用的。
  • @Ed:我想只打印一个回车,然后输入行的第一部分在很大程度上取决于终端的类型。
  • @user3121023:这些代码并没有真正标准化。他应该为此使用图书馆。
  • 在实践中,在 Posix 上,这些已经足够标准了;他们将在任何重要的地方工作。虽然,它在 Windows 上完全不同(而且 IMO 更好......)。不过,棘手的部分是按时获得输入。这意味着将 tty 更改为原始模式 (tcsetattr) 并可能使用 select 或循环中的某些内容检查输入。但是我在这里建议 readline 库类似于 bash - 它使用相同的库,并且在 C 代码中也很容易使用。请注意,它是 GPL。

标签: c linux shell


【解决方案1】:

ANSI 转义序列允许您随意在屏幕上移动光标。这对于在 shell 下运行的程序生成的全屏用户界面很有用,但也可以在提示中使用。这不适用于不接受保存和恢复光标位置代码的终端仿真器。更多关于移动光标的转义序列,请查看cursor movement

如果您需要更便携的解决方案,请使用curses,它是一个终端控制库,用于管理应用程序在类 Unix 系统的字符单元终端上的显示。执行系统上的 curses 库根据终端类型发送正确的控制字符。在ncurses 中使用int wmove(WINDOW* win, int y, int x),将光标移动到新位置。有关如何在 ncurses 下编程的更多信息,请参阅 NCurses-programing-howto

【讨论】:

    【解决方案2】:

    左右移动光标。在换行符或字符过多时停止输入

    #include <stdio.h>
    #include <string.h>
    #include <termios.h>
    #include <unistd.h>
    #include <ctype.h>
    #include <sys/select.h>
    #include <sys/ioctl.h>
    
    #define ESC          27
    #define INSERT       50
    #define DELETE       51
    #define PGUP         53
    #define PGDN         54
    #define ARROWRIGHT   67
    #define ARROWLEFT    68
    #define END          70
    #define HOME         72
    #define OTHER        79
    #define BRACKETLEFT  91
    #define TILDE       126
    #define BACKSPACE   127
    
    #define SIZE         30
    
    static const int STDIN = 0;
    
    int kbhit(void)
    {
        int bytesWaiting;
    
        ioctl(STDIN, FIONREAD, &bytesWaiting);
        return bytesWaiting;
    }
    
    int main ( ) {
        char input[SIZE] = {'\0'};
        int insert = 0;
        int each = 0;
        int end = 0;
        int to = 0;
        int ch = 0;
        int row = 0;
        int col = 0;
        struct termios oldattr, newattr;
    
        //set terminal
        tcgetattr( STDIN, &oldattr );
        newattr = oldattr;
        newattr.c_lflag &= ~( ICANON | ECHO );
        tcsetattr( STDIN, TCSANOW, &newattr );
        setbuf(stdin, NULL);
    
        printf ( "\033[2J");//clear screen
        printf ( "\033[25;1H");//move cursor to row 25 col 1
        printf ( "OVW");
        printf ( "\033[9;1H");//move cursor to row 9 col 1
        printf ( "enter your text ");//prompt
        //printf ( "%s", input);
        printf ( "\033[9;17H");//move cursor to row 9 col 17
        col = 17;
        row = 9;
        while ( ( ch = getchar ()) != '\n') {
            if ( isprint( ch)) {
                if ( insert && each < end && end < SIZE-3) {
                    //expand
                    end++;
                    for ( to = end; to >= each; to--) {
                        input[to + 1] = input[to];
                    }
                    printf ( "\033[9;17H");//move cursor to row 9 col 12
                    printf ( "\033[K");//erase to end of line
                    printf ( "%s", input);
                }
                printf ( "\033[%d;%dH", row, col);
                printf ( "%c", ch);
                input[each] = ch;
                each++;
                if ( each > end) {
                    end = each;
                }
                col++;
                if ( each == end) {
                    input[each] = '\0';
                }
                if ( each >= SIZE-1) {
                    break;
                }
                continue;
            }
    
            if ( ch == BACKSPACE) {
                if ( each) {
                    each--;
                    col--;
                    //contract
                    for ( to = each; to <= end; to++) {
                        input[to] = input[to + 1];
                    }
                    end--;
                    printf ( "\033[9;17H");//move cursor to row 1 col 7
                    printf ( "\033[K");//erase to end of line
                    printf ( "%s", input);
                    printf ( "\033[%d;%dH", row, col);
                }
            }
            if ( ch == ESC) {
                if ( !kbhit ( )) {
                    continue;
                }
                ch = getchar ( );
                if ( ch == OTHER) {
                    ch = getchar ( );
                    if ( ch == HOME) {
                        col -= each;
                        each = 0;
                        printf ( "\033[%d;%dH", row, col);
                        ch = getchar ( );
                    }
                    if ( ch == END) {
                        col += end - each;
                        each = end;
                        printf ( "\033[%d;%dH", row, col);
                        ch = getchar ( );
                    }
                }
                if ( ch == BRACKETLEFT) {
                    ch = getchar ( );
                    if ( ch == INSERT) {
                        ch = getchar ( );
                        if ( ch == TILDE) {
                            insert = !insert;
                            printf ( "\033[25;1H");//move cursor to row 25 col 1
                            if ( insert) {
                                printf ( "INS");
                            }
                            else {
                                printf ( "OVW");
                            }
                            printf ( "\033[%d;%dH", row, col);
                        }
                    }
                    if ( ch == DELETE) {
                        ch = getchar ( );
                        if ( ch == TILDE) {
                            //contract
                            for ( to = each; to <= end; to++) {
                                input[to] = input[to + 1];
                            }
                            end--;
                            printf ( "\033[9;17H");//move cursor to row 10 col 1
                            printf ( "\033[K");//erase to end of line
                            printf ( "%s", input);
                            printf ( "\033[%d;%dH", row, col);
                        }
                    }
                    if ( ch == ARROWRIGHT) {
                        if ( each < end) {
                            printf ( "\033[C");//cursor right
                            each++;
                            col++;
                        }
                    }
                    if ( ch == ARROWLEFT) {
                        if ( each) {
                            printf ( "\033[D");//cursor left
                            each--;
                            col--;
                        }
                    }
                }
                else {
                    ungetc ( ch, stdin);
                }
            }
        }
        printf ( "\n\ninput was [%s]\n", input);
        printf ( "\n\nbye\n");
    
        //restore terminal
        tcsetattr( STDIN, TCSANOW, &oldattr );
    
        return 0;
    }
    

    【讨论】:

      【解决方案3】:

      使用termios 和控制台代码(VT100 兼容 - 不可移植):

      #include <stdio.h>
      #include <string.h>
      #include <termios.h>
      #include <unistd.h>
      
      #define cursorforward(x) printf("\033[%dC", (x))
      #define cursorbackward(x) printf("\033[%dD", (x))
      
      #define KEY_ESCAPE  0x001b
      #define KEY_ENTER   0x000a
      #define KEY_UP      0x0105
      #define KEY_DOWN    0x0106
      #define KEY_LEFT    0x0107
      #define KEY_RIGHT   0x0108
      
      static struct termios term, oterm;
      
      static int getch(void);
      static int kbhit(void);
      static int kbesc(void);
      static int kbget(void);
      
      static int getch(void)
      {
          int c = 0;
      
          tcgetattr(0, &oterm);
          memcpy(&term, &oterm, sizeof(term));
          term.c_lflag &= ~(ICANON | ECHO);
          term.c_cc[VMIN] = 1;
          term.c_cc[VTIME] = 0;
          tcsetattr(0, TCSANOW, &term);
          c = getchar();
          tcsetattr(0, TCSANOW, &oterm);
          return c;
      }
      
      static int kbhit(void)
      {
          int c = 0;
      
          tcgetattr(0, &oterm);
          memcpy(&term, &oterm, sizeof(term));
          term.c_lflag &= ~(ICANON | ECHO);
          term.c_cc[VMIN] = 0;
          term.c_cc[VTIME] = 1;
          tcsetattr(0, TCSANOW, &term);
          c = getchar();
          tcsetattr(0, TCSANOW, &oterm);
          if (c != -1) ungetc(c, stdin);
          return ((c != -1) ? 1 : 0);
      }
      
      static int kbesc(void)
      {
          int c;
      
          if (!kbhit()) return KEY_ESCAPE;
          c = getch();
          if (c == '[') {
              switch (getch()) {
                  case 'A':
                      c = KEY_UP;
                      break;
                  case 'B':
                      c = KEY_DOWN;
                      break;
                  case 'C':
                      c = KEY_LEFT;
                      break;
                  case 'D':
                      c = KEY_RIGHT;
                      break;
                  default:
                      c = 0;
                      break;
              }
          } else {
              c = 0;
          }
          if (c == 0) while (kbhit()) getch();
          return c;
      }
      
      static int kbget(void)
      {
          int c;
      
          c = getch();
          return (c == KEY_ESCAPE) ? kbesc() : c;
      }
      
      int main(void)
      {
          int c;
      
          while (1) {
              c = kbget();
              if (c == KEY_ENTER || c == KEY_ESCAPE || c == KEY_UP || c == KEY_DOWN) {
                  break;
              } else
              if (c == KEY_RIGHT) {
                  cursorbackward(1);
              } else
              if (c == KEY_LEFT) {
                  cursorforward(1);
              } else {
                  putchar(c);
              }
          }
          printf("\n");
          return 0;
      }
      

      【讨论】:

        【解决方案4】:

        使用 ANSI 转义序列的简单示例:

        #include <stdio.h>
        
        
        int main()
        {
            char *string = "this is a string";
            char input[1024] = { 0 };
            printf("%s", string);
            /* move the cursor back 5 spaces */
            printf("\033[D");
            printf("\033[D");
            printf("\033[D");
            printf("\033[D");
            printf("\033[D");
            fgets(input, 1024, stdin);
            return 0;
        }
        

        为了非常有用,终端需要使用 termios.h 和/或 curses.h/ncurses.h 进入规范模式。这样,退格键代码可以被捕获并立即响应,并将缓冲区相应地绘制到屏幕上。以下是如何使用tcsetattr() 将终端设置为规范模式的示例:

        struct termios info;
        tcgetattr(0, &info);
        info.c_lflag &= ~ICANON;
        info.c_cc[VMIN] = 1;
        info.c_cc[VTIME] = 0;
        tcsetattr(0, TCSANOW, &info);
        

        另一个选项可能是使用readline()editline() 库。要使用 readline 库,请在编译器中指定 -lreadline。下面的代码sn -p可以用

        编译
        cc -lreadline some.c -o some
        
        
        #include <stdio.h>
        
        #include <readline/readline.h>
        #include <readline/history.h>
        
        int main()
        {
            char *inpt;
            int i = 0;
        
            while ( i < 10 )
            {
                inpt = readline("Enter text: ");
                add_history(inpt);
                printf("%s", inpt);
                printf("\n");
                ++i;
            }
            return 0;
        

        }

        【讨论】:

        • 我正在寻找它的行为类似于 readline,但没有导入 readline.h。我现在正在检查源代码,但欢迎提供一些关于如何实现函数 readline() 的帮助。
        • 实际上,终端通常以规范模式启动,您希望将其设置为非规范模式以使其返回退格而不是解释退格或等待换行符(这就是上面的tcsetattr 代码确实)
        • @user4832408-规范模式是什么意思?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-01-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-13
        • 1970-01-01
        相关资源
        最近更新 更多