終端的基本概念 每個進程都可以通過一個特殊的設備文件/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模式:將鍵盤端口上讀出的掃描碼放入緩沖區(qū) 2) Keycode mode (mediumraw) mediumraw模式:將掃描碼過濾為鍵盤碼放入緩沖區(qū) 3) ASCII mode (XLATE ) XLATE模式:識別各種鍵盤碼的組合,,轉換為TTY終端代碼放入緩沖區(qū) 4) UTF-8 MODE (UNICODE) Unicode 模式:UNICODE模式基本上與XLATE相同,,只不過可以通過數字小鍵盤間接輸入UNICODE代碼。
鍵盤碼好處理,,因此我們把鍵盤設備設置為keycode mode,。 #include <linux/kd.h> #include <sys/ioctl.h> #include <stdlib.h> 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 數據結構,并設置用來表示該終端特性和設置 的屬性值,??梢圆倏v由函數 tcgetattr()返回的數據結構來查詢和改變這些設置,當完成這 些操作時,,就使用 tesetattr()函數將用到的新值來更新終端,。函數 tcgetattr()和 tcsetattr()的調用形式分別如下。
int tcgetattr(int fd,struct termios *tp);
函數 tegetattr()將查詢和文件描述符 fd 相關聯的終端參數,,并將它們存儲到由 tp所引用的 termios 數據結構之中,。如果成功,則返回 0,,如果發(fā)生錯誤則返回-1,。
int tcsetattr(int fd,int action,struct termios *tp);
函數 tcsetattr()使用由 tp 引用的 termios 數據結構來設置和文件描述符 fd 相關聯的終端參數,參數 action 通過使用下面的參數值來控制什么時候發(fā)生改變,。 如果是 TCSANOW,,則表示立即改變這些屬性值; 如果是 TCSADRAIN,,則表示改變發(fā)生在所有 fd上的輸出都已經被發(fā)送到終端之后,,當要改變輸出設置時使用此函數; 如果是 TCSAFLUSH,,則表示改變發(fā)生在所有 fd上的輸出都已經被發(fā)送到終端之后,,任何掛起的輸入將被丟棄,。
函數 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表示控制協(xié)議,; 數組 c_cc 包含特殊字符序列的值以及它們所代表的操作,。 終端有兩種工作模式,分別為規(guī)范模式(或稱為 cooked 模式)和非規(guī)范模式(或稱為原始模式) ,。在規(guī)范模式下,,終端設備驅動程序處理特殊字符并以一次一行的方式將輸入發(fā)送給程序使用。而在非規(guī)范模式下,,大多數鍵盤輸入將得不到處理,也不緩存,。 以上說明來源《Linux 下的 C 編程》賈明 嚴世賢 編著
實現源碼: #include <termios.h> 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命令查看到其他標志的解釋,。 注冊信號處理函數 因為之前修改了鍵盤設備的模式和屬性,為了讓鍵盤設備的模式和屬性在程序在遇到異常被迫退出后能恢復到原來的狀態(tài),,所以需要注冊信號處理函數,。 例如: #include <signal.h> #include <unistd.h> // 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 <unistd.h> unsigned char buf[18]; int i,n; int exitflag=0; while(exitflag!=2) { n=read(fd,buf,sizeof(buf)); for(i=0;i<n;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。 |
|
來自: SamBookshelf > 《IO》