Команда IT специалистов выполнит подготовку инфраструктуры для вашего бизнеса.
Внедрение самых передовых решений и технологий.
Поддержка и сопровождение ваших сервисов.
Выполнение работ под "ключ", от покупки сервера, до настройки автоматизации процессов.
8(977)608-78-62 adm@nixm.ru

Перевод терминала в не канонический режим работы

Ответить
Аватара пользователя
nezabudka
Местный говорун
Местный говорун
Сообщения: 618
Зарегистрирован: 18 апр 2015, 06:13
Откуда: Ростов на Дону

Перевод терминала в не канонический режим работы

Сообщение nezabudka »

В линукс существует понятие "канонический" и
"не канонический" режим работы терминала. Канонический
подразумевает использование буфера, тоесть вводимые
с клавиатуры знаки будут буферизироваться до нажатия
клавиши "enter". В не канонический режим, в котором
кстати по умолчанию пребывает и консоль виндовс, мы
можем получить доступ несколькими путями. Во первых что это
нам дает. Давайте разберем это на примере скрипта bash.
Что бы получить ввод каждого символа с клавиатуры мы
в команду read подставим опцию -n со значением 1.
Теперь каждое нажатие на клавишу будет отправлять
значение символа сразу в программу. Здесь мы пока
не отключаем буферизованный ввод, а только определяем
размер буфера в один символ (1 или 2 байта в зависимости от локали).

Код: Выделить всё

#!/usr/bin/env bash
while [[ $d != 'q' ]]; do
        read -n 1 -s d
        echo -ne "$d "
done
echo ""
Что бы выйти из программы достаточно нажать клавишу 'q'.
Давайте теперь соберем похужую программу на Си работающую
пока в режиме канонического терминала, тоесть
в режиме по умолчанию.

Код: Выделить всё

#include <stdio.h>

int main(void)
{
        char ch; 
        while ((ch = getchar()) != 'q')
        {
                printf("%c\n", ch);
        }
        return 0;
}
Скомпилируем программу с опциями выводящими на всякий
случай, даже небольшие предупреждения:

Код: Выделить всё

gcc -Wall -O3 -o prog prog.c
Можем запускать Любую нажатую клавишу и символ перевода строки
программа будет повторять. Замечу, только однобайтовые
символы, тоесть аски, в латинской раскладке. Выход из
программы осуществляется так же по нажатию клавиши 'q'.
Что бы эта программа считывала сразу воодимые символы,
не дожидаясь нажатия клавиши "enter" можно в командной
строке перед запуском нашей программы перевести терминал
в режим "не канонический". Но для начала сохраним прежнее
значение в переменную:

Код: Выделить всё

save_stty=$(stty -g)
Теперь можно менять параметры:

Код: Выделить всё

stty -icanon
Запустим нашу программу: Теперь вывод появляется сразу же после нажатия на клавишу.
И так как мы теперь можем не нажимать клавишу "enter"
то и не поялвяются дублирующие невидимые занки перевода строки.
Нажмем клавишу 'q' и выйдем из программы, а терминал
переведем в нормальный режим восстановив первоначальные
его параметры:

Код: Выделить всё

stty $save_stty
Что бы программа работала так по умолчанию и мы могли перед каждым
ее запуском не менять в ручную настройки терминала вызовем
в программе утилиту stty через специальную функцию system()

Код: Выделить всё

#include <stdio.h>
#include <stdlib.h>
 
int main() {
    int c;
    system("stty -icanon -echo");   /* включаем неканонический ввод */
    while ((c = getchar()) != 'q') {
        printf("%c - %i\n", c, c); 
    }   
    system("stty icanon echo"); /* включаем канонический ввод */
    return 0;
}
Это очень просто но хочу заметить что вызывать в программе на Си
обертку с интефейсом командной строки далеко не продуктивно. Все это
можно проделать в ручную, вызывая по порядку необходимые функции.
Перепишим программу выполняющую все самостоятельно не обращаясь за
помощью к утилитам командной строки.

Код: Выделить всё

#include <stdio.h>
#include <unistd.h>
#include <termios.h>
 
struct termios oldt, newt; //определяем переменные

int main() {
    char ch; 
    tcgetattr(0, &oldt);
    newt = oldt;
    newt.c_lflag &= ~ICANON;
    tcsetattr(0, TCSANOW, &newt);

    while ((ch = getchar()) != 'q') {
        printf("%c\n", ch);
    }   

    tcsetattr(0, TCSANOW, &oldt);
    return 0;
}
Выходим только по каманде 'q'. Если выйти через сигнал
прерывания Ctrl+C то терминал останется работать в не
каноническом режиме. Это относится и к предыдущей программе.
Тогда во избежание неприятностей просто закройте терминал и
откройте снова. Справку по всем функциям можно получить не
отходя от кассы, потому что язык Си это язык на котором написан linux.
Подставляем в вызове страницы мана только циферку 3:

Код: Выделить всё

man 3 termios
man 3 tcgetattr
...
"I invented the term Object-Oriented and I can tell you I did not have C++ in mind." - Alan Kay
Olej

Re: Перевод терминала в не канонический режим работы

Сообщение Olej »

nezabudka писал(а):Перепишим программу выполняющую все самостоятельно не обращаясь за
помощью к утилитам командной строки.
А ещё, может окажется полезным в тему: Разработка программных проектов в Linux (глава "Расширенные операции ввода-вывода", стр. 273).

Ну и для полноты картины ... есть такая библиотека ncurses - это классика UNIX начиная с самых древних BSD, на ней сделано множество широкоизвестных используемых проектов Linux. ncurses и переводит терминал в некононический режим, и обеспечивает прямое позиционирование курсора, и посимвольный ввод/вывод ... и мн. других вещей. И когда окажется необходимым посимвольный ввод, то (хорошо понимая режимы ввода/вывода и termios) - лучше использовать ncurses.
Olej

Re: Перевод терминала в не канонический режим работы

Сообщение Olej »

Olej писал(а):И когда окажется необходимым посимвольный ввод, то (хорошо понимая режимы ввода/вывода и termios) - лучше использовать ncurses.
Вот как красиво выглядит практически полновесный визуальный редактор экрана (такой, как мог бы служить основой текстового редактора):

Код: Выделить всё

#include <signal.h>
#include <stdlib.h>
#include <ncurses.h>

void handler( int signo ) {
   endwin();                     // завершение работы с ncurses
   exit( 0 );
};

int main( int argc,char *argv[] ) {
   signal( SIGINT, handler );
   initscr();                    // инициализация (должна быть выполнена перед использованием ncurses)
   int c, y, x, my, mx;
   getmaxyx( stdscr, my, mx );
   move( my / 2, mx / 2 );       // перемещение курсора в стандартном экране y=10 x=30
   noecho();
   keypad( stdscr, TRUE );
   while( ( c = getch() ) != KEY_F( 3 ) ) {   
      getyx( stdscr, y, x );
      if( c == KEY_RESIZE ) {     
         getmaxyx( stdscr, my, mx );
         continue;
      }
      switch( c ) {
         case KEY_UP:    move( y > 0 ? --y : y, x ); break;
         case KEY_DOWN:  move( y < my ? ++y : y, x ); break;
         case KEY_LEFT:  move( y, x > 0 ? --x : x ); break;
         case KEY_RIGHT: move( y, x < mx ? ++x : x ); break;
         case KEY_BACKSPACE: if( x > 1 ) {
               move( y, x - 1 );
               addch( ' ' );
               move( y, x - 1 );
               break;
            }
         case KEY_DC: addch( ' ' ); break;
         default:
            addch( c );
            break;
      }
   }
   endwin();
   return 0;
}

Всего то! ;)
Аватара пользователя
nezabudka
Местный говорун
Местный говорун
Сообщения: 618
Зарегистрирован: 18 апр 2015, 06:13
Откуда: Ростов на Дону

Re: Перевод терминала в не канонический режим работы

Сообщение nezabudka »

Вот такой вот аналог программы получается с библиотекой ncurses:

Код: Выделить всё

#include <ncurses.h>

int main()
{
    char ch; 
    initscr();    
    cbreak();
    while(ch != 'q')
    {   
            ch = getch();
            printw("%c ", ch);
    }   
    endwin();
    return 0;
}
При компиляции укажем библиотеку для линковщика:

Код: Выделить всё

gcc -Wall -O3 -o prog prog.c -lncurses
Olej. Вы привели пример кода с управлением в окне при помощи стрелок. Подскажите при помощи чего
можно было бы реализовать управление в стиле vim, тоесть простого перемещение курсора по экрану
без отображения символов.
"I invented the term Object-Oriented and I can tell you I did not have C++ in mind." - Alan Kay
Olej

Re: Перевод терминала в не канонический режим работы

Сообщение Olej »

nezabudka писал(а): Вы привели пример кода с управлением в окне при помощи стрелок. Подскажите при помощи чего
можно было бы реализовать управление в стиле vim, тоесть простого перемещение курсора по экрану
без отображения символов.
Я не сильно понял что такое "без отображения символов" (не помню уже vim), но вот это и есть без отображения:

Код: Выделить всё

noecho();
P.S. ещё для спецклавиш может быть сильно полезно:

Код: Выделить всё

keypad( stdscr, TRUE );
Olej

Re: Перевод терминала в не канонический режим работы

Сообщение Olej »

nezabudka писал(а): Подскажите при помощи чего
2 полезных ссылки по ncurses:
http://alexber220.narod.ru/ncurses/
http://alexber220.narod.ru/ncurses/page2.htm
Только читать - аккуратно, там парень в залихватскости своей много неточностей пишет ... но как подсказка - годится.

А дальше: /usr/include/ncurses.h - там большинство вопросов можно разобрать из заголовков, определений и комментариев.
Аватара пользователя
nezabudka
Местный говорун
Местный говорун
Сообщения: 618
Зарегистрирован: 18 апр 2015, 06:13
Откуда: Ростов на Дону

Re: Перевод терминала в не канонический режим работы

Сообщение nezabudka »

Я имела ввиду аналогичную работу приведенной ниже программы только с использованием
библиотеки ncurses. Клавиши управления курсором в стиле vim "h j k l"

Код: Выделить всё

   #include <unistd.h>
   #include <stdlib.h>
   #include <fcntl.h>
   #include <termios.h>
   #include <stdio.h>
   int main (void) { 
   struct termios savetty, tty;
   char ch; 
   int x = 0, y = 0;
   if( !isatty( 0 ) ) { 
      fprintf( stderr, "stdin not terminal\n" );
      exit( EXIT_FAILURE );
   };  
   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 'k':
         printf( "%c[1A", 27 );
         break;
      case 'j':
         printf( "%c[1B", 27 );
         break;
      case 'l':
         printf( "%c[1C", 27 );
         break;
      case 'h':
         printf( "%c[1D", 27 );
         break;
      };  
      fflush( stdout );
   };  
   tcsetattr( 0, TCSAFLUSH, &savetty ); // восстановили состояние терминала
   printf( "\n" );
   exit( EXIT_SUCCESS );
Последний раз редактировалось nezabudka 26 июл 2016, 23:39, всего редактировалось 2 раза.
"I invented the term Object-Oriented and I can tell you I did not have C++ in mind." - Alan Kay
Olej

Re: Перевод терминала в не канонический режим работы

Сообщение Olej »

nezabudka писал(а):Я имела ввиду аналогичную работу приведенной ниже программы только с использованием
библиотеки ncurses. Клавиши управления курсором в стиле vim "h j k l"
В самом примитивном виде полный аналог вашего фрагмента будет так:

Код: Выделить всё

#include <ncurses.h> 

int main( int argc,char *argv[] ) {
   initscr();                   // инициализация
   int c, y, x;
   move( 10, 30 );       // перемещение курсора
   noecho();
   while( ( c = getch() ) != 'q' ) {
      getyx( stdscr, y, x );
      switch( c ) {
         case 'k': move( --y, x ); break;
         case 'j': move( ++y, x ); break;
         case 'h': move( y, --x ); break;
         case 'l': move( y, ++x ); break;
      }
   }
   endwin();
   return 0;
}
P.S. Но в таких программах нужно обязательно перехватывать сигнал SIGINT (^C) + по нему восстанавливать канонический режим терминала. Иначе по прерыванию ^C (что всем привычно) вы начисто теряете терминал.
Аватара пользователя
nezabudka
Местный говорун
Местный говорун
Сообщения: 618
Зарегистрирован: 18 апр 2015, 06:13
Откуда: Ростов на Дону

Re: Перевод терминала в не канонический режим работы

Сообщение nezabudka »

Код: Выделить всё

#include <ncurses.h>

int main(void)
{
  initscr();
  noecho();
  cbreak();

  int y,x;
  char ch;

  y=20; 
  x=40;
  move(y,x);
  curs_set(0);
  while(ch != 'q')
  {
          ch = getch();
          switch(ch)
          {
                case 'j':
                {
                        y=y+1;
                        move (y,x);
                        printw("*");
                        refresh();
                        break;
                }
                case 'k':
                {
                        y=y-1;
                        move (y,x);
                        printw("*");
                        refresh();
                        break;
                }
                case 'l':
                {
                        x=x+1;
                        move(y,x);
                        printw("*");
                        refresh();
                        break;
                }
                case 'h':
                {
                        x=x-1;
                        move (y,x);
                        printw("*");
                        refresh();
                        break;
                }
          }
  }
  endwin();
  return 0;
}
"I invented the term Object-Oriented and I can tell you I did not have C++ in mind." - Alan Kay
Olej

Re: Перевод терминала в не канонический режим работы

Сообщение Olej »

Шпак Дмитрий писал(а): А можног вопрос? ТЫ слышала про эхо? Я пока чуть выпивший, не стал вникать.
Когда сильно выпивши, то эхо гулко и многократно слышится, и отзывается внутри черепной коробки на любой малейший звук нестерпимым эхом... :shock: :lol:
Ответить

Вернуться в «C/C++»