分类: C/C++
2012-09-06 16:28:38
终端的基本概念
每个进程都可以通过一个特殊的设备文件/dev/tty访问它的控制终端。事实上每个终端设备都对应一个不同的设备文件,/dev/tty提供了一个通用的接口,一个进程要访问它的控制终端既可以通过/dev/tty也可以通过该终端设备所对应的设备文件来访问。
一台PC通常只有一套键盘和显示器,也就是只有一套终端设备,但是可以通过Ctrl-Alt-F1~Ctrl-Alt-F6切换到6个字符终端,相当于有6套虚拟的终端设备,它们共用同一套物理终端设备,对应的设备文件分别是/dev/tty1~/dev/tty6,所以称为虚拟终端(Virtual Terminal)。设备文件/dev/tty0表示当前虚拟终端,比如切换到Ctrl-Alt-F1的字符终端时/dev/tty0就表示/dev/tty1,切换到Ctrl-Alt-F2的字符终端时/dev/tty0就表示/dev/tty2,就像/dev/tty一样也是一个通用的接口,但它不能表示图形终端窗口所对应的终端。
打开键盘设备代码实现:
int getfd(const char *fnam)
{
int fd;
if(fnam)
{
fd=open_a_console(fnam);
if(fd>=0) return fd;
// printf(stderr,"Couldnt open %s\n",fnam);
exit(1);
}
printf("opening /dev/tty ...\n");
fd=open_a_console("/dev/tty");
if(fd>=0) return fd;
printf("opening /dev/tty0 ...\n");
fd=open_a_console("/dev/tty0");
if(fd>=0) return fd;
printf("opening /dev/vc/0 ...\n");
fd=open_a_console("/dev/vc/0");
if(fd>=0) return fd;
printf("opening /dev/console ...\n");
fd=open_a_console("/dev/console");
if(fd>=0) return fd;
for(fd=0;fd<3;fd++)
if(is_a_console(fd)) return fd;
// fprintf(stderr,"Couldnt get a file descriptor referring to the console\n");
exit(1); // total failure
}
static int is_a_console(int fd)
{
char arg;
arg = 0;
return
(ioctl(fd,KDGKBTYPE,&arg)==0&&((arg==KB_101)||(arg==KB_84)));
}
static int open_a_console(const char *fnam)
{
int fd;
// For ioctl purposes we only need some fd and permissions
// do not matter. But setfont:activatemap() does a write.
fd = open(fnam, O_RDWR);
if (fd < 0 && errno == EACCES)
fd = open(fnam, O_WRONLY);
if (fd < 0 && errno == EACCES)
fd = open(fnam, O_RDONLY);
if (fd < 0)
{
printf("can't open %s\n", fnam);
return -1;
}
if (!is_a_console(fd)) {
close(fd);
printf("it is not a console.\n");
return -1;
}
return fd;
}
键盘模式有4种:
1) Scancode mode (raw )raw模式:将键盘端口上读出的扫描码放入缓冲区
2) Keycode mode (mediumraw) mediumraw模式:将扫描码过滤为键盘码放入缓冲区
3) ASCII mode (XLATE ) XLATE模式:识别各种键盘码的组合,转换为TTY终端代码放入缓冲区
4) UTF-8 MODE (UNICODE) Unicode 模式:UNICODE模式基本上与XLATE相同,只不过可以通过数字小键盘间接输入UNICODE代码。
键盘码好处理,因此我们把键盘设备设置为keycode mode。
#include
#include
#include
if(ioctl(fd,KDSKBMODE, K_MEDIUMRAW))
{
perror("KDSKBMODE");
return 1;
}
但是在设置为keycode mode之前要保存当前的键盘模式,作恢复使用。
if(ioctl(fd,KDGKBMODE,&oldkbmode))
{
perror("KDGKBMODE");
exit(1);
}
设置键盘设备属性对终端的操作通过设置函数 tcgetattr()和函数 tcsetattr()来实现。其
中函数 tcgetattr()是用来初始化一个 termios 数据结构,并设置用来表示该终端特性和设置
的属性值。可以操纵由函数 tcgetattr()返回的数据结构来查询和改变这些设置,当完成这
些操作时,就使用 tesetattr()函数将用到的新值来更新终端。函数 tcgetattr()和 tcsetattr()的调用形式分别如下。
int tcgetattr(int fd,struct termios *tp);
函数 tegetattr()将查询和文件描述符 fd 相关联的终端参数,并将它们存储到由 tp所引用的 termios 数据结构之中。如果成功,则返回 0,如果发生错误则返回-1。
int tcsetattr(int fd,int action,struct termios *tp);
函数 tcsetattr()使用由 tp 引用的 termios 数据结构来设置和文件描述符 fd 相关联的终端参数,参数 action 通过使用下面的参数值来控制什么时候发生改变。
如果是 TCSANOW,则表示立即改变这些属性值;
如果是 TCSADRAIN,则表示改变发生在所有 fd上的输出都已经被发送到终端之后,当要改变输出设置时使用此函数;
如果是 TCSAFLUSH,则表示改变发生在所有 fd上的输出都已经被发送到终端之后,任何挂起的输入将被丢弃。
函数 tcgetattr()和函数 tcsetattr()均使用一个结构体,它是一个查询和操纵终端的标准接口,即 struct termios。该结构体的定义如下。
struct termios{
tcflag_t c_iflag;
tcflag_t c_oflag;
tcflag_t c_cflag;
tcflag_t c_lflag;
cc_t c_line;
cc_t c_cc[NCCS];
};
其中参数 c_iflag 用来控制输入处理选项;
参数 c_oflag控制输出数据的处理;
参数 c_cflag设置各种决定终端设备硬件特性的控制标志;
参数 c_lflag存放本地模式标志,用来操纵一些终端特性;
参数 c_line表示控制协议;
数组 c_cc 包含特殊字符序列的值以及它们所代表的操作。
终端有两种工作模式,分别为规范模式(或称为 cooked 模式)和非规范模式(或称为原始模式) 。在规范模式下,终端设备驱动程序处理特殊字符并以一次一行的方式将输入发送给程序使用。而在非规范模式下,大多数键盘输入将得不到处理,也不缓存。
以上说明来源《Linux 下的 C 编程》贾明 严世贤 编著
实现源码:
#include
if(tcgetattr(fd,&old)==-1)
perror("tcgetattr");
if(tcgetattr(fd,&_new)==-1)
perror("tcgetattr");
_new.c_lflag&=~(ICANON|ECHO|ISIG);
_new.c_iflag=0;
_new.c_cc[VMIN]=sizeof(buf);
_new.c_cc[VTIME]=1; // 0.1 sec intercharacter timeout
if(tcsetattr(fd,TCSAFLUSH,&_new)==-1)
perror("tcsetattr");
在程序中使用到的标志:
ICANON Enable canonical mode (described below).
ECHO Echo input characters.
VMIN Minimum number of characters for noncanonical read (MIN).
VTIME Timeout in deciseconds for noncanonical read (TIME).
可以通过man termios命令查看到其他标志的解释。
注册信号处理函数
因为之前修改了键盘设备的模式和属性,为了让键盘设备的模式和属性在程序在遇到异常被迫退出后能恢复到原来的状态,所以需要注册信号处理函数。
例如:
#include
#include
// the program terminates when there is no input for 10 secs
signal(SIGALRM, die);
// if we receive a signal, we want to exit nicely, in
// order not to leave the keyboard in an unusable mode
signal(SIGHUP, die);
signal(SIGINT, die);
signal(SIGQUIT, die);
signal(SIGILL, die);
signal(SIGTRAP, die);
signal(SIGABRT, die);
signal(SIGIOT, die);
signal(SIGFPE, die);
signal(SIGKILL, die);
signal(SIGUSR1, die);
signal(SIGSEGV, die);
signal(SIGUSR2, die);
signal(SIGPIPE, die);
signal(SIGTERM, die);
#ifdef SIGSTKFLT
signal(SIGSTKFLT, die);
#endif
signal(SIGCHLD, die);
signal(SIGCONT, die);
signal(SIGSTOP, die);
signal(SIGTSTP, die);
signal(SIGTTIN, die);
signal(SIGTTOU, die);
static void die(int x)
{
printf("caught signal %d, cleaning up...\n",x);
if (ioctl(fd,KDSKBMODE,oldkbmode))
{
perror("KDSKBMODE");
exit(1);
}
if (tcsetattr(fd, TCSANOW, &old) == -1)
perror("tcsetattr");
exit(1);
}
读取键盘设备键值
#include
unsigned char buf[18];
int i,n;
int exitflag=0;
while(exitflag!=2)
{
n=read(fd,buf,sizeof(buf));
for(i=0;i
{
printf(": %d %d\n", i, buf[i]);
if(buf[i]==105)
{
exitflag=1;
}
else if((buf[i]==106)&(exitflag==1))
{
exitflag=2;
break;
}
else
{
exitflag=0;
}
}
104个键的键盘按下的键盘码(keycode value):
注意:对应按键释放的键盘码等于按下的键盘码+128。