Неканонический режим терминального ввода

в ОС UNIX

Используемый "по умолчанию" в ОС UNIX режим ввода данных с клавиатуры терминала называется каноническим. Он подразумевает присутствие между клавиатурой терминала и прикладной программой очереди вводимых символов. Символы нажатых клавиш накапливаются в этой очереди до тех пор, пока пользователь не нажмет клавишу "Ввод" (Enter), и только в этот момент осуществляется передача строки накопленных символов в прикладную программу. Выданный в прикладной программе запрос на чтение с клавиатуры (посредством системного вызова read) приостанавливает выполнение программы до момента нажатия клавиши "Ввод".
Для некоторых прикладных программ (редакторов экранных форм, игр и т.п.) такой способ работы с клавиатурой неудобен. Поэтому ОС UNIX предоставляет разнообразные и гибкие средства для управления вводом с клавивтуры (в более широком смысле - асинхронными портами ЭВМ). Ниже описывается один из способов обеспечения "мгновенного" ввода кода символа с клавиатуры (без организации очереди символов, замыкаемой клавишой "Ввод") в режиме т.н. неканонического ввода.
  1. Включение необходимых файлов-заголовков
  2. 
    #include <fcntl.h>
    #include <termios.h>
    
  3. Объявление двух переменных для хранения управляющих структур
  4. 
    struct termios savetty;
    struct termios tty;
    
  5. Перевод драйвера клавиатуры в неканонический режим ввода
  6. 
    if ( !isatty(0) ) { /*Проверка: стандартный ввод - терминал?*/
      fprintf (stderr, "stdin not terminal\n");
      exit (1); /* Ст. ввод был перенаправлен на файл, канал и т.п. */
      };
    
    tcgetattr (0, &tty);
    savetty = tty; /* Сохранить упр. информацию канонического режима */
    tty.c_lflag &= ~(ICANON|ECHO|ISIG);
    tty.c_cc[VMIN] = 1;
    tcsetattr (0, TCSAFLUSH, &tty);
    

      Внимание. С данного момента клавиатурный ввод начинает работать в неканоническом режиме. При этом отключается эхо-вывод на экран вводимых символов, а также подавляется генерация сигналов специальными комбинациями клавиш (типа ^С, ^\).

      Предупреждение. Если Ваша программа завершит свою работу, не восстановив канонический режим (например, аварийно), то Вы вряд ли сможете продолжить продуктивную работу с оболочкой UNIX с данного терминала. Поэтому рекомендуется определить обработчик сигналов для наиболее распространенных ошибок, восстанавливающий канонический режим перед завершением программы. Восстановить канонический режим можно также попытаться с помощью команд reset или stty sane.

     
  7. Чтение символа с клавиатуры
  8. read (0, &ch, 1);
      Внимание. В неканоническом режиме не рекомендуется использовать библиотечные функции буферизованного ввода типа scanf, getc, getc и т.п., если Вы глубоко не понимаете, как они работают.
       
  9. Восстановление канонического режима ввода
  10. tcsetattr (0, TCSAFLUSH, &savetty);


Ниже приводится работающий прототип программы, использующей неканонический режим ввода с клавиатуры для перемещения курсора по экрану дисплея при нажатии клавиш "u", "d", "r" и "l". Символ "q" завершает работу программы.

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

main (argc, argv)
  int argc;
  char **argv;
  {
  struct termios savetty;
  struct termios tty;
  char ch;
  int x, y;

  printf ("Enter start position (x y): ");
  scanf ("%d %d", &x, &y);

  if ( !isatty(0) ) {
    fprintf (stderr, "stdin not terminal\n");
    exit (1);
    };

  tcgetattr (0, &tty);
  savetty = tty;
  tty.c_lflag &= ~(ICANON|ECHO|ISIG);
  tty.c_cc[VMIN] = 1;
  tcsetattr (0, TCSAFLUSH, &tty);

  printf ("%c[2J", 27);
  fflush (stdout);

  printf ("%c[%d;%dH", 27, y, x);
  fflush (stdout);

  for(;;)
    {
    read (0, &ch, 1);
    if (ch == 'q')
      break;
    switch (ch) 
      {
      case 'u':
        printf ("%c[1A", 27);
        fflush (stdout);
        break;
      case 'd':
        printf ("%c[1B", 27);
        fflush (stdout);
        break;
      case 'r':
        printf ("%c[1C", 27);
        fflush (stdout);
        break;
      case 'l':
        printf ("%c[1D", 27);
        fflush (stdout);
        break;
      };
    };

  tcsetattr (0, TCSAFLUSH, &savetty);
  exit (0);
  }