Uwe Ohse

Artikel

getch unter Unix?


Soetwas wie die DOS-Funktion getch stellt Unix nicht direkt zur Verfügung, wohl aber die Mittel es zu emulieren.
Dabei stellen sich folgende Probleme: Derartige Terminaleingenschaften werden heutzutage mit tcsetattr veraendert. Es folgt eine Beispielimplementation von getch:
#include <termios.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

#define MKFLAG(which) \
static int io_tio_set_flag_##which(int fd, int val, int on, int *old) \
{ struct termios tio; \
    if (tcgetattr(fd,&tio)) return -1; \
	if (old) *old=(tio.which & (val)); \
    if (on) tio.which |= (val); \
    else tio.which &= ~(val); \
    if (tcsetattr(fd,TCSADRAIN,&tio)) return -1; \
    return 0; \
} \
static int io_tio_get_flag_##which(int fd, int bit, int *value) \
{ struct termios tio; \
    if (tcgetattr(fd,&tio)) return -1; \
	*value=(tio.which & (bit)); \
    return 0; \
}
MKFLAG(c_lflag)

static int 
read_character_now_without_echo(int fd, char *c)
{
	int old_ICANON,old_ECHO;
	int restoreflag=0;
	int e=0;
	int retcode=-1;
	/* some devices do not support terminal flags. Notably:
	 * /dev/disk|floppy
	 * /dev/zero
	 * ...
	 * This function is able to handle them:
	 */
	if (-1==io_tio_set_flag_c_lflag(fd,ICANON,0,&old_ICANON)) {
		if (errno!=ENOTTY 
			&& errno!=EINVAL) { /* LINUX /dev/random lossage */
			e=errno;
			perror("Turnoff of ICANON failed");
			errno=e;
			return -1;
		}
	} else
		restoreflag=1;
	if (restoreflag && -1==io_tio_set_flag_c_lflag(fd,ECHO,0,&old_ECHO)) {
		e=errno;
		perror("Turnoff of ICANON failed");
	} else {
		if (restoreflag) restoreflag++;
		while (1) {
			retcode=read(fd,c,1);
			if (retcode==1 || retcode==0) break;
			if (errno==EINTR) continue;
			break;
		}
	}
	/* we do not check for errors anymore: If we were able to change
	 * a flag before and are unable to do that afterwards then we
	 * are in unsolvable trouble anyway.
	 * btw, it _could_ happen, in case revoke() is summoned upon
	 * us. Oh, well.
	 */
	/* set echo to old value */
	if (restoreflag==2)
		io_tio_set_flag_c_lflag(fd,ECHO,old_ECHO,NULL);
	/* back to canonical input mode (line by line) */
	if (restoreflag)
		io_tio_set_flag_c_lflag(0,ICANON,old_ICANON,NULL);
    errno=e;
	return retcode;
}

int main(void)
{
	char c;
	int r;

	r=read_character_now_without_echo(0,&c);
	if (r==-1) exit(1);
	if (r) {
		if (-1==write(1,&c,1)) 
			perror("write");
	}
	return (0);
}