Terminal Programming: 두 판 사이의 차이
imported>Drake |
imported>Drake 잔글 (→문자를 하나씩 읽어들이기) |
||
| 388번째 줄: | 388번째 줄: | ||
=== 문자를 하나씩 읽어들이기 === | === 문자를 하나씩 읽어들이기 === | ||
<source lang="c"> | |||
/* menu4.c */ | |||
#include <stdio.h> | |||
#include <unistd.h> | |||
#include <termios.h> | |||
char *menu[] = { | |||
"a - add new record", | |||
"d - delete record", | |||
"q - quit", | |||
NULL, | |||
}; | |||
int getchoice(char *greet, char *choices[], FILE *in, FILE *out); | |||
{ | |||
int chosen = 0; | |||
int selected; | |||
char **option; | |||
do { | |||
fprintf(out, "Choice: %s\n", greet); | |||
option = choices; | |||
while(*option) { | |||
fprintf(out, "%s\n", *option); | |||
option++; | |||
} | |||
do { | |||
selected = fgetc(in); | |||
} while (selected == '\n' || selected == '\r'); | |||
/* 1글자씩 받는 모드에서는 엔터키가 '\r'이 된다 */ | |||
option = choices; | |||
while(*option) { | |||
if(selected == *option[0]) { | |||
chosen = 1; | |||
break; | |||
} | |||
option++; | |||
} | |||
if(!chosen) { | |||
fprintf(out, "Incorrect choice, select again\n"); | |||
} | |||
} while(!chosen); | |||
return selected; | |||
} | |||
int main() | |||
{ | |||
int choice = 0; | |||
FILE *input; | |||
FILE *output; | |||
struct termios initial_settings, new_settings; | |||
if(!isatty(fileno(stdout))) { | |||
fprintf(stderr, "You are not a terminal, OK.\n"); | |||
} | |||
input = fopen("/dev/tty", "r"); | |||
output = fopen("/dev/tty", "w"); | |||
if(!input || !output) { | |||
fprintf(stderr, "Unable to open /dev/tty\n"); | |||
return 1; | |||
} | |||
/* 문자를 한글자씩 받기 위해 터미널의 특성을 변경 */ | |||
tcgetattr(fileno(input), &initial_settings); | |||
new_seettings = initial_settings; | |||
new_settings.c_lflag &= ~ICANON; | |||
new_settings.c_lflag &= ~ECHO; | |||
new_settings.c_cc[VMIN] =1; | |||
new_settings.c_cc[VTIME] = 0; | |||
new_settings.c_lflag &= ~ISIG; | |||
if(tcsetattr(fileno(input), TCSANOW, &new_settings) != 0) { | |||
fprintf(stderr, "Could not set attributes\n"); | |||
} | |||
do { | |||
choice = getchoice("Please select an action", menu, input, output); | |||
printf("You have chosen: %c\n", choice); | |||
} while(choice != 'q'); | |||
/* 문자를 한글자씩 받으려고 터미널 특성 변경했던걸 복구 */ | |||
tcsetattr(fileno(input), TCSANOW, &initial_settings); | |||
return 0; | |||
} | |||
</source> | |||
* 이제 좀 즉시 처리되는 형태가 된것 같다. | |||
=== 터미널 형태 식별 === | |||
* 터미널은 여러 종류가 있다. 터미널 에뮬레이터에서 어떤 방식으로 에뮬레이트할건지 정해본적이 있을 거다. vt100이나 vt220같은것들.. | |||
* 다음과 같이 입력하면 터미널의 종류를 식별할 수 있다. | |||
<source lang="bash"> | |||
drake@debian:~$ echo $TERM | |||
linux | |||
drake@debian:~$ | |||
</source> | |||
* 얼마전까지는 xterm이었으나, 많은 터미널 에뮬레이터가 linux를 지원해주는 추세라 옮겨가고 있다. | |||
* 예전엔 프로그래머가 터미널의 종류에 따라 다 신경써야 했지만, terminfo라는게 생겨서, 아예 그 안에 웬만한건 다 들어있다. | |||
* terminfo에 대한 정보는, 원래는 /usr/lib/terminfo/v/vt100 이런 경로에 있었지만, 현재는 /usr/share/terminfo/v/vt100 이런 경로로 정의되어 있고, 컴파일된 파일로 제공된다. | |||
* 속도때문인지 /lib/share/terminfo/v/vt100 과 같이, 기본 경로에 제공되기도 한다. | |||
* <font color="red">추후 원본 파일을 볼 수 있는 방법을 찾아 기술하도록 함</font> | |||
=== setupterm === | |||
* 마찬가지로, ncurses 라이브러리를 사용한다. gcc 컴파일시 -lncurses 옵션을 붙여서 컴파일해야 한다. | |||
<source lang="c"> | |||
#include <term.h> | |||
int setupterm(char *term, int fildes, int *errret); | |||
</source> | |||
* setupterm은 터미널 형태를 변수 term으로 세팅한다. term이 널 포인터라면 기본값을 사용한다. fildes는 터미널용 File Descriptor다. 함수의 결과는 errret가 널이 아닐 경우 errret가 가리키는 정수 변수에 저장된다. 그 값은 다음과 같다. | |||
# -1 - terminfo 데이터베이스가 없음 | |||
# 0 - terminfo 데이터베이스에 해당하는게 없음 | |||
# 1 - 성공 | |||
* setupterm함수는 성공시 OK, 실패시 ERR을 반환한다. | |||
<source lang="c"> | |||
/* badterm.c */ | |||
#include <stdio.h> | |||
#include <nterm.h> | |||
#include <ncurses.h> | |||
int main() | |||
{ | |||
setupterm("unlisted", fileno(stdout), (int *)0); | |||
printf("Done.\n"); | |||
return 0; | |||
} | |||
</source> | |||
* 여기서 눈여겨보아야 할 것은, Done이 출력되지 않는다는 점이다. | |||
=== tigetflag, tigetnum, tigetstr === | |||
<source lang="c"> | |||
#include <term.h> | |||
int tigetflag(char *capname); | |||
int tigetnum(char *capname); | |||
char *tigetstr(char *capname); | |||
</source> | |||
* ini 파일좀 다뤄봤다면 어느정도 이해가 빠를거다. | |||
* tigetflag는 플래그를 반환. 실패시 -1 반환 | |||
* tigetnum은 정수 반환. 실패시 -2 반환 | |||
* tigetstr은 문자열 반환. 실패시 (char *) -1 반환 | |||
<source lang="c"> | |||
/* sizeterm.c */ | |||
#include <stdio.h> | |||
#include <term.h> | |||
#include <ncurses.h> | |||
int main() | |||
{ | |||
int nrows, ncolumns; | |||
setupterm(NULL, fileno(stdout), (int *)0); | |||
nrows = tigetnum("lines"); | |||
ncolumns = tigetnum("cols"); | |||
printf("This terminal has %d columns and %d rows\n", ncolumns, nrows); | |||
return 0; | |||
} | |||
</source> | |||
* 이 어플리케이션은 터미널 크기를 반환한다. | |||
=== tparm === | |||
<source lang="c"> | |||
#include <term.h> | |||
char *tparm(char *cap, long p1, long p2, ..., long p9); | |||
</source> | |||
* terminfo의 각 항을 바꾼다. 거의 쓰이지 않음.<s>존나 쓸까말까 고민했는데, 걍 있다는거 정도는 알아야 된다고 생각</s> | |||
=== putp, tputs === | |||
<source lang="c"> | |||
#include <term.h> | |||
int putp(char *const str); | |||
int tputs(char *const str, int affcnt, int (*putfunc)(int)); | |||
</source> | |||
* putp는 터미널 제어 문자열을 취해서 stdout으로 보낸다. 성공시 OK를, 실패시 ERR 반환.<s>개같은 putfunc 어떻게 설명하라고</s> | |||
<source lang="c"> | |||
char *cursor; | |||
char *esc_sequence; | |||
cursor = tigetstr("cup"); | |||
esc_sequence = tparm(cursor, 5, 30); | |||
putp(esc_sequence); | |||
</source> | |||
* 아래로 다섯칸, 오른쪽으로 30칸 이동한다. | |||
<source lang="c"> | |||
/* screenmenu.c */ | |||
#include <stdio.h> | |||
#include <unistd.h> | |||
#include <termios.h> | |||
#include <term.h> | |||
#inlcude <curses.h> | |||
static FILE *output stream = (FILE *)0; | |||
char *menu[] = { | |||
"a - add new record", | |||
"d - delete record", | |||
"q - quit", | |||
NULL, | |||
}; | |||
int char_to_terminal(int char_to_write) | |||
{ | |||
if (output_stream) putc(char_to_write, output_stream); | |||
return 0; | |||
} | |||
int getchoice(char *greet, char *choices[], FILE *in, FILE *out); | |||
{ | |||
int chosen = 0; | |||
int selected; | |||
int screenrow, screencol = 10; | |||
char **option; | |||
char *cursor, *clear; | |||
output_stream = out; | |||
setupterm(NULL, fileno(out), (int *)0); | |||
cursor = tigetstr("cup"); | |||
clear = tigetstr("clear"); | |||
screenrow = 4; | |||
tputs(clear, 1, char_to_terminal); | |||
tputs(tparm(cursor, screenrow, screencol), 1, char_to_terminal); | |||
fprintf(out, "Choice: %s", greet); | |||
screenrow += 2; | |||
option = choices; | |||
while(*option) { | |||
tputs(tparm(cursor, screenrow, screencol), 1, char_to_terminal); | |||
fprintf(out, "%s", *option); | |||
screenrow++; | |||
option++; | |||
} | |||
do { | |||
selected = fgetc(in); | |||
option = choices; | |||
while(*option) { | |||
if(selected == *option[0]) { | |||
chosen = 1; | |||
break; | |||
} | |||
option++; | |||
} | |||
if(!chosen) { | |||
tputs(tparm(cursor, screenrow, screencol), 1, char_to_terminal); | |||
fprintf(out, "Incorrect choice, select again\n"); | |||
} | |||
} while(!chosen); | |||
tputs(clear, 1, char_to_terminal); | |||
return selected; | |||
} | |||
int main() | |||
{ | |||
int choice = 0; | |||
FILE *input; | |||
FILE *output; | |||
struct termios initial_settings, new_settings; | |||
if(!isatty(fileno(stdout))) { | |||
fprintf(stderr, "You are not a terminal, OK.\n"); | |||
} | |||
input = fopen("/dev/tty", "r"); | |||
output = fopen("/dev/tty", "w"); | |||
if(!input || !output) { | |||
fprintf(stderr, "Unable to open /dev/tty\n"); | |||
return 1; | |||
} | |||
/* 문자를 한글자씩 받기 위해 터미널의 특성을 변경 */ | |||
tcgetattr(fileno(input), &initial_settings); | |||
new_seettings = initial_settings; | |||
new_settings.c_lflag &= ~ICANON; | |||
new_settings.c_lflag &= ~ECHO; | |||
new_settings.c_cc[VMIN] =1; | |||
new_settings.c_cc[VTIME] = 0; | |||
new_settings.c_lflag &= ~ISIG; | |||
if(tcsetattr(fileno(input), TCSANOW, &new_settings) != 0) { | |||
fprintf(stderr, "Could not set attributes\n"); | |||
} | |||
do { | |||
choice = getchoice("Please select an action", menu, input, output); | |||
printf("You have chosen: %c\n", choice); | |||
sleep(1); /* 선택된 화면이 너무 빠르게 지나가버리기 때문에, 1초간 대기하여 사람이 볼 수 있을 정도로 지나가게 함.. */ | |||
} while(choice != 'q'); | |||
/* 문자를 한글자씩 받으려고 터미널 특성 변경했던걸 복구 */ | |||
tcsetattr(fileno(input), TCSANOW, &initial_settings); | |||
return 0; | |||
} | |||
</source> | |||
2014년 6월 19일 (목) 06:27 판
메뉴
Basic
<source lang="c"> /* menu1.c */ /* getchoice 원형 정리 */
- include <stdio.h>
char *menu[] = {
"a - add new record", "d - delete record", "q - quit", NULL,
};
- menu를 출력하고 사용자의 입력을 받는다.
- main은 menu를 사용하여 getchoice를 호출
int getchoice(char *greet, char *choices[]) {
int chosen = 0; int selected; char **option;
do {
printf("Choice: %s\n", greet);
option = choices;
while(*option) {
printf("%s\n", *option);
option++;
}
selected = getchar();
option = choices;
while(*option) {
if(selected == *option[0]) {
chosen =1;
break;
}
option++;
}
if(!chosen) {
printf("Incorrect choice, select again\n");
}
} while(!chosen);
return selected;
}
/* main 함수는 menu를 사용하여 getchoice를 호출한다. */ int main() {
int choice = 0;
do
{
choice = getchoice("Please select an action", menu);
printf("You have chosen: %c\n", choice);
} while (choice != 'q');
return 0;
} </source>
- 기본적인 메뉴 프로그램이다.
문제가 많다
isatty
<source lang="c">
- include <unistd.h>
int isatty(int fildes); </source>
- isatty는 fildes가 터미널이면 1, 아니면 0을 반환한다.
<source lang="c"> /* menu2.c */ /* getchoice 원형 정리 */
- include <unistd.h>
- include <stdio.h>
char *menu[] = {
"a - add new record", "d - delete record", "q - quit", NULL,
};
- menu를 출력하고 사용자의 입력을 받는다.
- main은 menu를 사용하여 getchoice를 호출
int getchoice(char *greet, char *choices[]) {
int chosen = 0; int selected; char **option;
do {
printf("Choice: %s\n", greet);
option = choices;
while(*option) {
printf("%s\n", *option);
option++;
}
selected = getchar();
option = choices;
while(*option) {
if(selected == *option[0]) {
chosen =1;
break;
}
option++;
}
if(!chosen) {
printf("Incorrect choice, select again\n");
}
} while(!chosen);
return selected;
}
int main() {
int choice = 0;
if(!isatty(fileno(stdout))) {
fprintf(stderr, "You are not a terminal!\n");
return 1;
}
do {
choice = getchoice("Please select an action", menu);
printf("You have chosen: %c\n", choice);
} while (choice != 'q');
return 0;
} </source>
- 콘솔이 아닐때 에러를 뱉어준다.
<source lang="c">
- include <stdio.h>
- include <unistd.h>
char *menu[] = {
"a - add new record", "d - delete record", "q - quit", NULL,
};
int getchoice(char *greet, char *choices[], FILE *in, FILE *out); {
int chosen = 0; int selected; char **option;
do {
fprintf(out, "Choice: %s\n", greet);
option = choices;
while(*option) {
fprintf(out, "%s\n", *option);
option++;
}
do {
selected = fgetc(in);
} while (selected == '\n');
option = choices;
while(*option) {
if(selected == *option[0]) {
chosen = 1;
break;
}
option++;
}
if(!chosen) {
fprintf(out, "Incorrect choice, select again\n");
}
} while(!chosen);
return selected;
}
int main() {
int choice = 0;
FILE *input;
FILE *output;
if(!isatty(fileno(stdout))) {
fprintf(stderr, "You are not a terminal, OK.\n");
}
input = fopen("/dev/tty", "r");
output = fopen("/dev/tty", "w");
if(!input || !output) {
fprintf(stderr, "Unable to open /dev/tty\n");
return 1;
}
do {
choice = getchoice("Please select an action", menu, input, output);
printf("You have chosen: %c\n", choice);
} while(choice != 'q');
return 0;
} </source>
- 이제 리다이렉션해도 터미널 출력으로만 나오게 된다.
tcgetattr, tgsetattr
- 이건 ncurses 라이브러리가 필요. gcc 컴파일시에 -lncurses 옵션을 붙이면 된다.
<source lang="c">
- include <termios.h>
struct termios {
tcflag_t c_iflag; tcflag_t c_oflag; tcflag_t c_cflag; tcflag_t c_lflag; cc_t c_cc[NCCS];
}
int tcgetattr(int fildes, struct termios *termios_p); int tcsetattr(int fildes, int actions, const struct termios *termios_p); </source>
- tcgetattr은 fildes가 가리키고 있는 터미널 정보를 가져와 termios_p 구조체에 들어간다.
- tcsetattr의 actions는 다음중 하나로 지정
- TCSANOW - 즉시 값 변경
- TCSADRAIN - 출력이 끝났을때 값 변경
- TCSAFLUSH - 출력이 끝났을때 값 변경, 유효한 입력이나 read호출에서 리턴되지 않은 입력은 취소.
- 입력모드 c_iflag에서 사용할 수 있는 매크로는 다음과 같다.
- BRKINT - 라인에서 break 조건이 감지되면 인터럽트를 발생
- IGNBRK - 라인에서 break 조건을 무시
- ICRNL - 입력된 캐리지 리턴을 뉴라인으로 변환
- IGNCR - 캐리지 리턴은 무시
- INLGR - 입력된 새 라인을 캐리지 리턴으로 변환
- IGNPAR - 입력된 문자중 패리티 에러가 있는 문자는 무시
- INPCK - 입력 문자들에 대해 패리티 체크를 한다.
- PARMRK - 패리티 에러 표시
- ISTRIP - 입력되는 모든 문자들을 7비트로 strip
- IXOFF - 입력시에 소프트웨어 흐름 제어
- IXON - 출력시 소프트웨어 흐름 제어
- BRKINT나 IGNBRK가 설정되지 않으면 break 조건은 0x00으로 읽힘
- 출력모드 c_oflag에서 사용할 수 있는 매크로는 다음과 같다.
- OPOST - 출력시 처리. 세팅 안하면 밑엣것들 전부 무시.
- ONLCR - 출력시 뉴 라인을 캐리지 리턴과 라인피드로 변환
- OCRNL - 출력시 캐리지 리턴을 뉴라인으로 변환
- ONOCR - 첫문자일땐 캐리지 리턴을 출력하지 않음
- ONLRET - 뉴라인을 캐리지 리턴으로 취급
- OFILL - 지연을 위해 채움문자로 보냄
- OFDEL - 채움 문자로 NULL 대신 DEL을 사용
- NLDLY - 새 라인시 딜레이
- CRDLY - 캐리지리턴시 딜레이
- TABDLY - 탭시 딜레이
- BSDLY - 백스페이스 딜레이
- VTDLY - 수직탭 딜레이
- FFDLY - 폼피드 딜레이
- 컨트롤 모드 c_cflag에서 사용할 수 있는 매크로는 다음과 같다.
- CLOCAL - 상태라인 무시
- CREAD - 문자 읽기 가능
- CS5 - 문자 보내고 받을때 5비트 사용
- CS6 - 문자 보내고 받을때 6비트 사용
- CS7 - 문자 보내고 받을때 7비트 사용
- CS8 - 문자 보내고 받을때 8비트 사용
- CSTOPB - 정지비트 2비트
- HUPCL - 전송 끊기, Hang-up 세팅.
- PARENB - 패리티 사용
- PARODD - 홀수 패리티 사용
- 로컬 모드 c_lflag에서 사용할 수 있는 매크로는 다음과 같다.
- ECHO - 입력되는 문자의 로컬 echo를 가능하게 한다.
- ECHOE - erase 문자를 받으면 backspace, space, backspace를 수행.
- ECHOK - kill 문자를 받으면 라인을 지운다.
- ECHONL - 새 라인 문자도 echo한다.
- ICANON - 정규 입력 처리를 가능하게 한다.
- IEXTEN - 입력 처리시 특별히 정의한 함수를 사용가능하게
- IESIG - 시그널을 가능하게 한다.
- NOFLSH - 큐는 플러시 안한다.
- TOSTOP - 쓰기시도시 백그라운드 프로세스에 시그널 보냄
- 제어모드에서 ICANON 세팅여부에 따라 특수제어모드 세팅이 가능
- ICANON이 세팅되었을때 특수제어모드 c_cc배열의 인덱스는 다음과 같다.
- VEOF - EOF문자
- VEOL - EOL문자
- VERASE - ERASE 문자
- VINTR - INTR문자
- VKILL - KILL 문자
- VQUIT - QUIT 문자
- VSUSP - SUSP 문자
- VSTART - START 문자
- VSTOP - STOP 문자
- ICANON이 세팅되어 있지 않을 때 특수제어모드 c_cc배열의 인덱스는 다음과 같다.
- VINTR - INTR 문자
- VMIN - MIN 값
- VQUIT - QUIT문자
- VSUSP - SUSP문자
- VTIME - TIME값
- VSTART - START 문자
- VSTOP - STOP문자
- 문자에 대한 설명은 다음과 같다.
- INTR - 터미널 드라이버가 터미널과 연결된 프로세스에 SIGINT 시그널을 보내도록 한다.
- QUIT - 터미널 드라이버가 터미널과 연결된 프로세스에 SIGQUIT 시그널을 보내도록 한다.
- ERASE - 터미널 드라이버가 라인의 마지막 문자를 지우도록 한다.
- KILL - 터미널 드라이버가 라인을 전부 지우도록 한다.
- EOF - 터미널 드라이버가 라인의 모든 문자들을 응용프로그램의 입력으로 전달한다. 라인이 비었다면 READ호출은 파일의 끝에서 시도한 것처럼 0문자를 리턴
- EOL - 라인중단. 뉴라인 문자가 사용된다.
- SUSP - 터미널 드라이버가 터미널과 연결된 프로세스에 SIGSUSP시그널을 보내도록 한다.
- STOP - 터미널에 출력되지 않도록 흐름정지시킨다. XON/XOFF 흐름제어 제공. 보통 Ctrl-S로 설정
- START - STOP 이후에 다시 출력하게 한다. 보통 아스키 XON문자 사용
- Time과 Min값
- Min = 0, Time = 0 - read는 즉시 리턴. 읽을것이 없으면 읽지 않음
- Min = 0, Time > 0 - read는 읽어들일 문자가 있거나 Time/10이 경과했을 때 리턴. 정해진 시간동안 문자가 읽혀지지 않는다면 read는 0 리턴
- Min > 0, Time = 0 - read는 min개의 문자를 읽을때까지 기다리고, 읽어들인 문자의 갯수 리턴. 파일의 끝이라면 0 리턴
- Min > 0, Time > 0 - read가 호출되면 읽어들일 문자를 기다린다. read는 min개의 문자를 읽어들였거나 time/10초만큼 경과했을때 리턴. 이건 ESC키를 눌렀을때 코드값과 기능키 눌렀을때 첫번째 코드값의 차이점을 알아내는데 유용하게 사용될수 있다. 네트워크 통신이나 상위 프로세서는 이러한 타이머 정보를 쉽게 지울수 있다.
- ICANON으로 설정하지 않고 min과 time값을 사용하면 프로그램은 입력되는 모든 글자를 처리할 수 있다.
쉘에서 터미널 모드 접근
- 터미널의 termios 설정값을 알아보려면 다음의 명령으로 알아볼 수 있다.
<source lang="bash"> $ stty -a </source>
- 리눅스에서 출력은 다음과 같다.
drake@debian:~$ stty -a speed 38400 baud; rows 27; columns 93; line = 0; intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0; -parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel -iutf8 opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke drake@debian:~$
- EOF문자가 Ctrl+D고, echo가 설정되어 있음을 알 수 있다.
- 테스트하다가 이상해지면 다음과 같이 복구할 수 있다.
<source lang="bash"> $ stty sane </source>
- 쉘스크립트가 한번에 한문자씩 처리하도록 하려면 min을 1로 설정하고 time을 0으로 설정하고 icanon을 빼야 한다.
<source lang="bash"> $ stty -icanon min 1 time 0 </source>
- 비번검사를 한다든지 할땐 프롬프트를 보내기 전에 echo를 끌 수 있다.
<source lang="bash"> $ stty -echo </source>
- 나중에 다시 가능하게 하려면
<source lang="bash"> $ stty echo </source>
cfgetispeed, cfgetospeed, cfsetispeed, cfsetospeed
<source lang="c">
- include <termios.h>
speed_t cfgetispeed(const sturct termios *); speed_t cfgetospeed(const struct termios *); int cfsetispeed(struct termios *, speed_t speed); int cfsetospeed(struct termios *, speed_t speed); </source>
- tcsetattr로 termios를 만들어 써야 한다.
- speed는 다음과 같다.
- B0 - 접속 끊음
- B1200 - 1200baud
- B2400 - 2400baud
- B9600 - 9600baud
- B38400 - 38400baud
- 표준속도는 38400이 최대, 비표준 속도는 setserial로 사용
tcdrain, tcflow, tcflush
<source lang="c">
- include <termios.h>
int tcdrain(int fildes); int tcflow(int fildes, int flowtype); int tcflush(int fldes, int in_out_selector); </source>
- tcdrain은 출력이 다 될때까지 기다리는 함수
- tcflow는 출력을 중단하거나 재개한다.
- tcflush는 입력이나 출력을 지워버린다.
비밀번호 입력 프로그램
<source lang="c"> /* password.c */
- include <termios.h>
- include <stdio.h>
- define PASSWORD_LEN 32
int main() {
struct termios initialrsettings, newrsettings; char password[PASSWORD_LEN + 1]; /* 다음 표준 입력의 현재 설정값을 얻고, 이전에 만들둔 termios 구조체에 그걸 복사한다. */ tcgetattr(fileno(stdin), &initialrsettings); /* 마지막 설정을 복구하기 위해 원래의 설정을 복사하고, newrsettings에서 echo를 끄고 사용자에게 비밀번호를 물어보다. */ newrsettings = initialrsettings; newrsettings.c_lflag &= ~ECHO;
printf("Enter password: ");
/* 터미널 속성을 newrsettings으로 설정하고 비번을 읽어들인다. 마지막에는 터미널 속성을 원래의 설정값으로 복구하고 비번을 출력한다. */
if(tcsetattr(fileno(stdin), TCSAFLUSH, &newrsettings) != 0) {
fprintf(stderr, "Could not set attributes\n");
}
else {
fgets(password, PASSWORD_LEN, stdin);
tcsetattr(fileno(stdin), TCSANOW, &initialrsettings);
fprintf(stdout, "\nYou entered %s\n", password);
}
exit(0);
} </source>
문자를 하나씩 읽어들이기
<source lang="c"> /* menu4.c */
- include <stdio.h>
- include <unistd.h>
- include <termios.h>
char *menu[] = {
"a - add new record", "d - delete record", "q - quit", NULL,
};
int getchoice(char *greet, char *choices[], FILE *in, FILE *out); {
int chosen = 0;
int selected;
char **option;
do {
fprintf(out, "Choice: %s\n", greet);
option = choices;
while(*option) {
fprintf(out, "%s\n", *option);
option++;
}
do {
selected = fgetc(in);
} while (selected == '\n' || selected == '\r');
/* 1글자씩 받는 모드에서는 엔터키가 '\r'이 된다 */
option = choices;
while(*option) {
if(selected == *option[0]) {
chosen = 1;
break;
}
option++;
}
if(!chosen) {
fprintf(out, "Incorrect choice, select again\n");
}
} while(!chosen);
return selected;
}
int main() {
int choice = 0; FILE *input; FILE *output; struct termios initial_settings, new_settings;
if(!isatty(fileno(stdout))) {
fprintf(stderr, "You are not a terminal, OK.\n");
}
input = fopen("/dev/tty", "r");
output = fopen("/dev/tty", "w");
if(!input || !output) {
fprintf(stderr, "Unable to open /dev/tty\n");
return 1;
}
/* 문자를 한글자씩 받기 위해 터미널의 특성을 변경 */
tcgetattr(fileno(input), &initial_settings);
new_seettings = initial_settings;
new_settings.c_lflag &= ~ICANON;
new_settings.c_lflag &= ~ECHO;
new_settings.c_cc[VMIN] =1;
new_settings.c_cc[VTIME] = 0;
new_settings.c_lflag &= ~ISIG;
if(tcsetattr(fileno(input), TCSANOW, &new_settings) != 0) {
fprintf(stderr, "Could not set attributes\n");
}
do {
choice = getchoice("Please select an action", menu, input, output);
printf("You have chosen: %c\n", choice);
} while(choice != 'q');
/* 문자를 한글자씩 받으려고 터미널 특성 변경했던걸 복구 */ tcsetattr(fileno(input), TCSANOW, &initial_settings); return 0;
} </source>
- 이제 좀 즉시 처리되는 형태가 된것 같다.
터미널 형태 식별
- 터미널은 여러 종류가 있다. 터미널 에뮬레이터에서 어떤 방식으로 에뮬레이트할건지 정해본적이 있을 거다. vt100이나 vt220같은것들..
- 다음과 같이 입력하면 터미널의 종류를 식별할 수 있다.
<source lang="bash"> drake@debian:~$ echo $TERM linux drake@debian:~$ </source>
- 얼마전까지는 xterm이었으나, 많은 터미널 에뮬레이터가 linux를 지원해주는 추세라 옮겨가고 있다.
- 예전엔 프로그래머가 터미널의 종류에 따라 다 신경써야 했지만, terminfo라는게 생겨서, 아예 그 안에 웬만한건 다 들어있다.
- terminfo에 대한 정보는, 원래는 /usr/lib/terminfo/v/vt100 이런 경로에 있었지만, 현재는 /usr/share/terminfo/v/vt100 이런 경로로 정의되어 있고, 컴파일된 파일로 제공된다.
- 속도때문인지 /lib/share/terminfo/v/vt100 과 같이, 기본 경로에 제공되기도 한다.
- 추후 원본 파일을 볼 수 있는 방법을 찾아 기술하도록 함
setupterm
- 마찬가지로, ncurses 라이브러리를 사용한다. gcc 컴파일시 -lncurses 옵션을 붙여서 컴파일해야 한다.
<source lang="c">
- include <term.h>
int setupterm(char *term, int fildes, int *errret); </source>
- setupterm은 터미널 형태를 변수 term으로 세팅한다. term이 널 포인터라면 기본값을 사용한다. fildes는 터미널용 File Descriptor다. 함수의 결과는 errret가 널이 아닐 경우 errret가 가리키는 정수 변수에 저장된다. 그 값은 다음과 같다.
- -1 - terminfo 데이터베이스가 없음
- 0 - terminfo 데이터베이스에 해당하는게 없음
- 1 - 성공
- setupterm함수는 성공시 OK, 실패시 ERR을 반환한다.
<source lang="c"> /* badterm.c */
- include <stdio.h>
- include <nterm.h>
- include <ncurses.h>
int main() {
setupterm("unlisted", fileno(stdout), (int *)0);
printf("Done.\n");
return 0;
} </source>
- 여기서 눈여겨보아야 할 것은, Done이 출력되지 않는다는 점이다.
tigetflag, tigetnum, tigetstr
<source lang="c">
- include <term.h>
int tigetflag(char *capname); int tigetnum(char *capname); char *tigetstr(char *capname); </source>
- ini 파일좀 다뤄봤다면 어느정도 이해가 빠를거다.
- tigetflag는 플래그를 반환. 실패시 -1 반환
- tigetnum은 정수 반환. 실패시 -2 반환
- tigetstr은 문자열 반환. 실패시 (char *) -1 반환
<source lang="c"> /* sizeterm.c */
- include <stdio.h>
- include <term.h>
- include <ncurses.h>
int main() {
int nrows, ncolumns;
setupterm(NULL, fileno(stdout), (int *)0);
nrows = tigetnum("lines");
ncolumns = tigetnum("cols");
printf("This terminal has %d columns and %d rows\n", ncolumns, nrows);
return 0;
} </source>
- 이 어플리케이션은 터미널 크기를 반환한다.
tparm
<source lang="c">
- include <term.h>
char *tparm(char *cap, long p1, long p2, ..., long p9); </source>
- terminfo의 각 항을 바꾼다. 거의 쓰이지 않음.
존나 쓸까말까 고민했는데, 걍 있다는거 정도는 알아야 된다고 생각
putp, tputs
<source lang="c">
- include <term.h>
int putp(char *const str); int tputs(char *const str, int affcnt, int (*putfunc)(int)); </source>
- putp는 터미널 제어 문자열을 취해서 stdout으로 보낸다. 성공시 OK를, 실패시 ERR 반환.
개같은 putfunc 어떻게 설명하라고
<source lang="c"> char *cursor; char *esc_sequence; cursor = tigetstr("cup"); esc_sequence = tparm(cursor, 5, 30); putp(esc_sequence); </source>
- 아래로 다섯칸, 오른쪽으로 30칸 이동한다.
<source lang="c"> /* screenmenu.c */
- include <stdio.h>
- include <unistd.h>
- include <termios.h>
- include <term.h>
- inlcude <curses.h>
static FILE *output stream = (FILE *)0;
char *menu[] = {
"a - add new record", "d - delete record", "q - quit", NULL,
};
int char_to_terminal(int char_to_write) {
if (output_stream) putc(char_to_write, output_stream); return 0;
}
int getchoice(char *greet, char *choices[], FILE *in, FILE *out); {
int chosen = 0; int selected; int screenrow, screencol = 10;
char **option; char *cursor, *clear;
output_stream = out;
setupterm(NULL, fileno(out), (int *)0);
cursor = tigetstr("cup");
clear = tigetstr("clear");
screenrow = 4;
tputs(clear, 1, char_to_terminal);
tputs(tparm(cursor, screenrow, screencol), 1, char_to_terminal);
fprintf(out, "Choice: %s", greet);
screenrow += 2;
option = choices;
while(*option) {
tputs(tparm(cursor, screenrow, screencol), 1, char_to_terminal);
fprintf(out, "%s", *option);
screenrow++;
option++;
}
do {
selected = fgetc(in);
option = choices;
while(*option) {
if(selected == *option[0]) {
chosen = 1;
break;
}
option++;
}
if(!chosen) {
tputs(tparm(cursor, screenrow, screencol), 1, char_to_terminal);
fprintf(out, "Incorrect choice, select again\n");
}
} while(!chosen);
tputs(clear, 1, char_to_terminal);
return selected;
}
int main() {
int choice = 0;
FILE *input;
FILE *output;
struct termios initial_settings, new_settings;
if(!isatty(fileno(stdout))) {
fprintf(stderr, "You are not a terminal, OK.\n");
}
input = fopen("/dev/tty", "r");
output = fopen("/dev/tty", "w");
if(!input || !output) {
fprintf(stderr, "Unable to open /dev/tty\n");
return 1;
}
/* 문자를 한글자씩 받기 위해 터미널의 특성을 변경 */
tcgetattr(fileno(input), &initial_settings);
new_seettings = initial_settings;
new_settings.c_lflag &= ~ICANON;
new_settings.c_lflag &= ~ECHO;
new_settings.c_cc[VMIN] =1;
new_settings.c_cc[VTIME] = 0;
new_settings.c_lflag &= ~ISIG;
if(tcsetattr(fileno(input), TCSANOW, &new_settings) != 0) {
fprintf(stderr, "Could not set attributes\n");
}
do {
choice = getchoice("Please select an action", menu, input, output);
printf("You have chosen: %c\n", choice);
sleep(1); /* 선택된 화면이 너무 빠르게 지나가버리기 때문에, 1초간 대기하여 사람이 볼 수 있을 정도로 지나가게 함.. */
} while(choice != 'q');
/* 문자를 한글자씩 받으려고 터미널 특성 변경했던걸 복구 */
tcsetattr(fileno(input), TCSANOW, &initial_settings);
return 0;
} </source>