终端驱动程序的模式
以一个简短的例子作为开始:
/* rotate.c : map a->b, b->c, .. z->a
* purpose: useful for showing tty modes
*/
#include <stdio.h>
#include <ctype.h>
int main()
{
int c;
while ( ( c=getchar() ) != EOF ){
if ( c == 'z' )
c = 'a';
else if (islower(c))
c++;
putchar(c);
}
}
总结:
1.假如我们在敲回车前输入abx然后删除x,程序中未得到输入的字符x
2.敲击键盘的同时字符显示在屏幕上,但是直到按了回车键,程序才收到输入;
3.Ctrl+C键结束输入并终止程序
上述程序不做这些操作,缓冲、回显、编辑和控制处理都由驱动程序完成。
缓冲和编辑包含规范处理,此时终端连接处于规范模式。
非规范模式
命令stty -icanon关闭了驱动程序中的规范模式处理,由于非规范模式中没有缓冲,输入字符'a'直接输出处理结果'b'。用户输入未被缓冲可能是一件麻烦事。当用户试图删除一个字符,驱动程序不能做任何事情,字符早就传送给程序了。
下面演示处于规范模式与非规范模式下执行情况,输入均为:abx(删除x)cd efg(Ctrl +C)
编写play_again程序
很多应用程序,例如自动取款机和计算机游戏,都会向用户提出yes/no的问题。下面是程序最终的源代码:
/* play_again4.c
* purpose: ask if user wants another transaction
* method: set tty into chr-by-chr, no-echo mode
* set tty into no-delay mode
* read char, return result
* resets terminal modes on SIGINT, ignores SIGQUIT
* returns: 0=>yes, 1=>no, 2=>timeout
* better: reset terminal mode on Interrupt
*/
#include <stdio.h>
#include <termios.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#define ASK "Do you want another transaction"
#define TRIES 3 /* max tries */
#define SLEEPTIME 2 /* time per try */
#define BEEP putchar('\a') /* alert user */
/*
* skip over non-legal chars and return y,Y,n,N or EOF
*/
char get_ok_char()
{
int c;
while( ( c = getchar() ) != EOF && strchr("yYnN",c) == NULL )
;
return c;
}
int get_response( char *question , int maxtries)
/*
* purpose: ask a question and wait for a y/n answer or timeout
* method: use getchar and complain about non-y/n input
* returns: 0=>yes, 1=>no
*/
{
int input;
printf("%s (y/n)?", question); /* ask */
fflush(stdout); /* force output */
while ( 1 ){
sleep(SLEEPTIME); /* wait a bit */
input = tolower(get_ok_char()); /* get next chr */
if ( input == 'y' )
return 0;
if ( input == 'n' )
return 1;
if ( maxtries-- == 0 ) /* outatime? */
return 2; /* sayso */
BEEP;
}
}
void set_cr_noecho_mode()
/*
* purpose: put file descriptor 0 into chr-by-chr mode and noecho mode
* method: use bits in termios
*/
{
struct termios ttystate;
tcgetattr( 0, &ttystate); /* read curr. setting */
ttystate.c_lflag &= ~ICANON; /* no buffering */
ttystate.c_lflag &= ~ECHO; /* no echo either */
ttystate.c_cc[VMIN] = 1; /* get 1 char at a time */
tcsetattr( 0 , TCSANOW, &ttystate); /* install settings */
}
void set_nodelay_mode()
/*
* purpose: put file descriptor 0 into no-delay mode
* method: use fcntl to set bits
* notes: tcsetattr() will do something similar, but it is complicated
*/
{
int termflags;
termflags = fcntl(0, F_GETFL); /* read curr. settings */
termflags |= O_NONBLOCK; /* flip on nodelay bit */
fcntl(0, F_SETFL, termflags); /* and install 'em */
}
/* how == 0 => save current mode, how == 1 => restore mode */
/* this version handles termios and fcntl flags */
void tty_mode(int how)
{
static struct termios original_mode;
static int original_flags;
static int stored = 0;
if ( how == 0 ){
tcgetattr(0, &original_mode);
original_flags = fcntl(0, F_GETFL);
stored = 1;
}
else if ( stored ) {
tcsetattr(0, TCSANOW, &original_mode);
fcntl( 0, F_SETFL, original_flags);
}
}
void ctrl_c_handler(int signum)
/*
* purpose: called if SIGINT is detected
* action: reset tty and scram
*/
{
tty_mode(1);
exit(2);
}
int main()
{
int response;
void ctrl_c_handler(int);
tty_mode(0); /* save current mode */
set_cr_noecho_mode(); /* set -icanon, -echo */
set_nodelay_mode(); /* noinput => EOF */
signal( SIGINT, ctrl_c_handler ); /* handle INT */
signal( SIGQUIT, SIG_IGN ); /* ignore QUIT signals */
response = get_response(ASK, TRIES); /* get some answer */
tty_mode(1); /* reset orig mode */
return response;
}
在上面代码中我们捕获SIGINT,重置驱动程序,然后返回。