/*
 * tv.c		control WatchMate, tuner board for PC
 * Copyright(c)1994 by Hiroaki Sengoku
 * Version 0.00	May 22, 1994	by sengoku@sdl.hitachi.co.jp
 */

#define	TV_C	0x269
#define	TV_D	0x26a

#ifndef TV_CONFIG
#define	TV_CONFIG	"/usr/local/lib/tv.config"
#endif

#include	<stdio.h>
#include	<time.h>
#ifdef BSD
#include	<termios.h>
#include	<sys/ioctl.h>
#include	<sys/file.h>
#else
#include	<termio.h>
#endif

#define	ON		1
#define	OFF		0
#define	true	1
#define	false	0

#define	VAL_MAX	63	/* max value of volume, bright, ... */
int	volume   = 32;
int volume_timer = 0;
int mute_flag = false;

#define	NChTV		62		/* $B%A%c%s%M%k?t(B */
#define	NChCable	125
typedef enum { ModeTV, ModeCable, ModeLinear, ModeVideo, NMode } Mode;
char *channel_name[NMode] = { " ", "c", " ", "v" };
int channel_max[NMode] = { NChTV, NChCable, 0x8000, 1 };
int channel[NMode] = { 1, 29, 0x0496, 1 };
Mode channel_mode = ModeTV;
int channel_timer = 0;
int direct_channel = 0;
int direct_timer = 0;

#define	FineCenter	32
short tvch_fine[NChTV+1];
long tvch_table[NChTV+1] = { NChTV,
	0x04960, 0x049c0, 0x04a20, 0x08e60, 0x08ec0, 0x08f20,	/*  1 -  6 */
	0x08f80, 0x08fc0, 0x09020, 0x09080, 0x090e0, 0x09140,	/*  7 - 12 */
	0x0a120, 0x0a180, 0x0a1e0, 0x0a240, 0x0a2a0, 0x0a300,	/* 13 - 18 */
	0x0a360, 0x0a3c0, 0x0a420, 0x0a480, 0x0a4e0, 0x0a540,	/* 19 - 24 */
	0x0a5a0, 0x0a600, 0x0a660, 0x0a6c0, 0x0a720, 0x0a780,	/* 25 - 30 */
	0x0a7e0, 0x0a840, 0x0a8a0, 0x0a900, 0x0a960, 0x0a9c0,	/* 31 - 36 */
	0x0aa20, 0x0aa80, 0x0aae0, 0x0ab40, 0x0aba0, 0x0ac00,	/* 37 - 42 */
	0x0ac60, 0x0acc0, 0x0ad20, 0x0ad80, 0x0ade0, 0x0ae40,	/* 43 - 48 */
	0x0aea0, 0x0af00, 0x0af60, 0x0afc0, 0x0b020, 0x0b080,	/* 49 - 54 */
	0x0b0e0, 0x0b140, 0x0b1a0, 0x0b200, 0x0b260, 0x0b2c0,	/* 55 - 60 */
	0x0b320, 0x0b380 };										/* 61 - 62 */

short cvch_fine[NChCable+1];
long cvch_table[NChCable+1] = { NChCable,
	0x045f0, 0x04650, 0x046b0, 0x04710, 0x047b0, 0x04810,	/*   1 -   6 */
	0x08dd0, 0x08e30, 0x08e90, 0x08ef0, 0x08f50, 0x08fb0,	/*   7 -  12 */
	0x04a80, 0x04ae0, 0x04b40, 0x04ba0, 0x04c00, 0x04c60,	/*  13 -  18 */
	0x04cc0, 0x04d20, 0x04d80, 0x08e00, 0x091a0, 0x09200,	/*  19 -  24 */
	0x09260, 0x092c0, 0x09320, 0x09380, 0x093e0, 0x09440,	/*  25 -  30 */
	0x094a0, 0x09500, 0x09560, 0x095c0, 0x09620, 0x09680,	/*  31 -  36 */
	0x096e0, 0x09740, 0x097a0, 0x09800, 0x09860, 0x098c0,	/*  37 -  42 */
	0x09920, 0x09980, 0x099e0, 0x09a40, 0x09aa0, 0x09b00,	/*  43 -  48 */
	0x09b60, 0x09bc0, 0x09c20, 0x09c80, 0x09ce0, 0x09d40,	/*  49 -  54 */
	0x09da0, 0x09e00, 0x09e60, 0x09ec0, 0x09f20, 0x09f80,	/*  55 -  60 */
	0x09fe0, 0x0a040, 0x0a0a0, 0x11df0, 0x11e50, 0x11eb0,	/*  61 -  66 */
	0x11f10, 0x11f70, 0x11fd0, 0x12030, 0x12090, 0x120f0,	/*  67 -  72 */
	0x12150, 0x121b0, 0x12210, 0x12270, 0x122d0, 0x12330,	/*  73 -  78 */
	0x12390, 0x123f0, 0x12450, 0x124b0, 0x12510, 0x12570,	/*  79 -  84 */
	0x125d0, 0x12630, 0x12690, 0x126f0, 0x12750, 0x127b0,	/*  85 -  90 */
	0x12810, 0x12870, 0x128d0, 0x12930, 0x12990, 0x129f0,	/*  91 -  96 */
	0x12a50, 0x12ab0, 0x12b10, 0x12b70, 0x12bd0, 0x12c30,	/*  97 - 102 */
	0x12c90, 0x12cf0, 0x12d50, 0x12db0, 0x12e10, 0x12e70,	/* 103 - 108 */
	0x12ed0, 0x12f30, 0x12f90, 0x12ff0, 0x13050, 0x130b0,	/* 109 - 114 */
	0x13110, 0x13170, 0x131d0, 0x13230, 0x13290, 0x132f0,	/* 115 - 120 */
	0x13350, 0x133b0, 0x13410, 0x13470, 0x134d0 };			/* 121 - 125 */

short *channel_fine[NMode] = { tvch_fine, cvch_fine };
long *channel_table[NMode] = { tvch_table, cvch_table };

typedef enum { AdjFine, AdjBright, AdjCont, AdjSharp, AdjColor, AdjTint,
				   NAdj } Adjust;
char *adjust_name[NAdj]
	= { "FINETUNE","BRIGHT  ","C0NTRAST","SHARP   ","C0L0R   ","TINT    " };
int adjust_value[NAdj]
	= { 0,         25,        50,        45,        36,        45 };
int adjust_setting[NAdj]
	= { 0x000,     0x800,     0x200,     0x300,     0x400,     0x500 };
Adjust adjust_mode = 0;

int clock_flag = false;

extern int errno;

static void inline port_out(unsigned char value, unsigned short port) {
__asm__ volatile ("outb %b0,%1"
		::"a" (value),"d" (port));
}

static unsigned char inline port_in(unsigned short port) {
	unsigned char value;
__asm__ volatile ("inb %1,%0"
		:"=a" (value):"d" (port));
	return value;
}

void send_ctl(c)
unsigned char c;
{
	int i;
	port_out(c,TV_C);
	for( i=0; i < 20; i++ )	;	/* busy wait */
}

void send_data(c)
unsigned char c;
{
	int i;
	port_out(c,TV_D);
	for( i=0; i < 20; i++ )	;	/* busy wait */
}

/*  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
 0  $B#0(B $B#1(B $B#2(B $B#3(B $B#4(B $B#5(B $B#6(B $B#7(B $B#8(B $B#9(B $B!'(B $B!c(B $B!d(B $B!](B $B!%(B $B!$(B
 1  $B!!(B $B#A(B $B#B(B $B#C(B $B#D(B $B#E(B $B#F(B $B#G(B $B#H(B $B#I(B $B#J(B $B#K(B $B#L(B $B#M(B $B#N(B $B"#(B
 2  $B#P(B $B#Q(B $B#R(B $B#S(B $B#T(B $B#U(B $B#V(B $B#W(B $B#X(B $B#Y(B $B#Z(B $BO?(B $B2h(B $B:F(B $B@8(B $BAa(B
 3  $BAw(B $B4,(B $BLa(B $BG/(B $B7n(B $BF|(B $B2;(B $B:M(B $B<g(B $BI{(B $B@<(B $B"*(B $B"+(B $B",(B $B"-(B $B"#(B
 4  $B%S(B $B%G(B $B%*(B $B%9(B $B%F(B $B%l(B $B%a(B $B%$(B $B%s(B $B%5(B $B%V(B $B%+(B $B%i(B $B!<(B $B%#(B $B%H(B
 5  $B!)(B $B#a(B $B#b(B $B#c(B $B#d(B $B#e(B $B#f(B $B#g(B $B#h(B $B#i(B $B#j(B $B#k(B $B#l(B $B#m(B $B#n(B $B#o(B
 6  $B#p(B $B#q(B $B#r(B $B#s(B $B#t(B $B#u(B $B#v(B $B#w(B $B#x(B $B#y(B $B#z(B $B!((B $B!_(B $B!?(B $B!&(B *1
 7  *2 *3 *4 *5 *6 *7 *8 $B"v(B *9 *a *b *c *d *e *f *g

 *1  $B@c$@$k$^(B		*6  $BF^$j(B ($BF^(B)	*b  $B7V8wEt(B		*g $BF)L@(B
 *2  $BEEOC(B			*7  $B1+(B   ($B;1(B)	*c  $B:8%9%T!<%+(B
 *3  $B=w(B				*8    ?			*d	$B1&%9%T!<%+(B
 *4  $BCK(B				*9  $B%+%;%C%H(B	*e  A $BLL:F@8(B
 *5  $B@2$l(B ($BB@M[(B)	*a  $BEE5e(B		*f  B $BLL:F@8(B
 */
void send_char(val)
int val;
{
	int i;
	send_ctl(0x2);
	for( i=0; i < 8; i++ ) {
		if( val & 0x80 ) {
			send_data(0x1);
			send_data(0x3);
		} else {
			send_data(0x0);
			send_data(0x2);
		}
		val <<= 1;
	}
	send_data(0x6);
	send_data(0x2);
}		

void draw_space(n)
int n;
{
	for( ; n > 0; n-- ) send_char(0x7f);
}

void clear() {
	draw_space(24*12);
}

void draw_char(c)
char c;
{
	switch(c) {
	  case ' ':	c = 0x7f;	break;
	  case ',':	c = 0x0f;	break;
	  case '-': c = 0x0d;	break;
	  case '.':	c = 0x0e;	break;
	  case '/':	c = 0x6d;	break;
	  case ':':	c = 0x0a;	break;
	  case ';':	c = 0x6b;	break;
	  case '<':	c = 0x0b;	break;
	  case '>':	c = 0x0c;	break;
	  case '?':	c = 0x50;	break;
	  case '@':	c = 0x3f;	break;
	  case 'O':	c = 0x00;	break;	/* O => 0 */
	  default:
		if( 'A' <= c && c <= 'Z' ) c += 0x11 - 'A';
		else if( 'a' <= c && c <= 'z' ) c += 0x51 - 'a';
		else if( '0' <= c && c <= '9' ) c -= '0';
	}
	send_char(c);
}

void draw_str(str)
char *str;
{
	unsigned char c;
	while(c=*str++) draw_char(c);
}

void move_cur(x,y)
int x, y;
{
	send_char(0xfd);
	send_char(0x90|(y&0x0f));
	send_char(0xfd);
	send_char(0xa0|(x&0x1f));
}

#define	BLACK	0
#define	BLUE	1
#define	GREEN	2
#define	CYAN	3
#define	RED		4
#define	PURPLE	5
#define	YELLOW	6
void set_color(col)
int col;
{
	send_char(0xfd);
	send_char(0x80|(col&0x0f));
}

void clear_channel() {	/* $B%A%c%s%M%kI=<($r>C$9(B */
	move_cur(20,0);
	draw_space(4);
}

void draw_mute() {		/* $B%_%e!<%HI=<((B */
	move_cur(9,5);
	set_color(RED);
	draw_str("MUTE");
}

void clear_mute() {		/* $B%_%e!<%HI=<($r>C$9(B */
	move_cur(9,5);
	draw_space(4);
}

void draw_clock() {		/* $B;~4V$rI=<($9$k(B */
	time_t t;
	struct tm *tp;
	char str[10];
	time(&t);
	tp = localtime(&t);
	move_cur(0,0);
	set_color(CYAN);
	sprintf(str,"%02d:%02d",tp->tm_hour,tp->tm_min);
	draw_str(str);
}

void clear_clock() {	/* $B;~4VI=<($r>C$9(B */
	move_cur(0,0);
	draw_space(5);
}

void flush_volume() {	/* $B%\%j%e!<%`I=<($r>C$9(B */
	adjust_mode = 0;
	move_cur(0,9);
	draw_space(24*2);
}

void tv_tune(f)
long f;
{
	int i;
	send_ctl(0x1);
	send_data(0x0);
	send_data(0x4);
	for( i=0; i < 18; i++ ) {
		if( f & 0x20000 ) {
			send_data(0x5);
			send_data(0x7);
			send_data(0x5);
		} else {
			send_data(0x4);
			send_data(0x6);
			send_data(0x4);
		}
		f <<= 1;
	}
	send_data(0x0);
}

void set_channel(table,fine_table,ch,fine)
long *table;
short *fine_table;
int ch, fine;
{
	long f;
	if( ch < 1 ) ch = 1;
	else if( ch > *table ) ch = *table;
	f = table[ch];
	if( fine >= 0 ) fine_table[ch] = fine;
	else if( fine_table[ch] < 0 ) fine_table[ch] = FineCenter;
	adjust_value[AdjFine] = fine_table[ch];
	f += fine_table[ch] - FineCenter;
	channel[ModeLinear] = f/16;
	tv_tune(f);
}

void tv_channel(mode,ch,fine)
Mode mode;
int ch, fine;
{
	long f;
	char str[10];
	flush_volume();
	switch(mode) {
	  case ModeTV:
	  case ModeCable:
		set_channel(channel_table[mode],channel_fine[mode],ch,fine);
		break;
	  case ModeLinear:
		f = ch*16;
		if( fine >= 0 ) f += fine - FineCenter;
		else adjust_value[AdjFine] = FineCenter;
		tv_tune(f);
		move_cur(20,0);
		set_color(RED);
		sprintf(str,"%4d",ch);
		draw_str(str);
		return;
	  case ModeVideo:
		move_cur(20,0);
		set_color(RED);
		draw_str("   V");
		channel_timer = 30;
		return;
	}
	move_cur(20,0);
	set_color(RED);
	sprintf(str,"%s%3d",channel_name[mode],ch);
	draw_str(str);
	channel_timer = 30;
}

void tv_channel_up(mode)
Mode mode;
{
	int ch, max;
	short *fine;
	ch = channel[mode];
	max = channel_max[mode];
	fine = channel_fine[mode];
	if( ch < max ) {
		ch++;
		if( mode == ModeTV || mode == ModeCable ) {
			while( fine[ch] < 0 && ch < max ) ch++;
			if( fine[ch] < 0 ) ch = channel[mode];
		}
	}
	if( ch != channel[mode] ) {
		channel[mode] = ch;
		tv_channel(mode,ch,-1);
	}
}

void tv_channel_down(mode)
Mode mode;
{
	int ch;
	short *fine;
	ch = channel[mode];
	fine = channel_fine[mode];
	if( ch > 1 ) {
		ch--;
		if( mode == ModeTV || mode == ModeCable ) {
			while( fine[ch] < 0 && ch > 1 ) ch--;
			if( fine[ch] < 0 ) ch = channel[mode];
		}
	}
	if( ch != channel[mode] ) {
		channel[mode] = ch;
		tv_channel(mode,ch,-1);
	}
}

void set_value(mode,val)
int mode;
int val;
{
	int i;
	val &= 0x3f;
	val |= mode;
	send_ctl(0x3);
	for( i=0; i < 12; i++ ) {
		if( val & 0x800 ) {
			send_data(0x1);
			send_data(0x3);
		} else {
			send_data(0x0);
			send_data(0x2);
		}
		val <<= 1;
	}
	send_data(0x6);
	send_data(0x2);
}

void draw_bar(val)
int val;
{
	int i;
	move_cur(0,10);
	set_color(CYAN);
	for( i=0; i < val/4; i++ ) draw_char('@');
	for( ; i < VAL_MAX/4; i++ ) draw_char(' ');
}

void tv_volume(vol)
int vol;
{
	char str[10];
	if( !mute_flag ) set_value(0x100,vol);
	move_cur(0,9);
	set_color(RED);
	draw_str("VOLUME  ");
	move_cur(13,9);
	set_color(YELLOW);
	sprintf(str,"%2d",vol);
	draw_str(str);
	draw_bar(vol);
	volume_timer = 30;	/* wait 3 sec before clear */
	adjust_mode = 0;
}

void tv_adjust(mode,val)
Adjust mode;
int val;
{
	char str[10];
	if( mode == AdjFine ) {
		tv_channel(channel_mode,channel[channel_mode],val);
	} else if( mode == AdjCont ) {
		set_value(adjust_setting[mode],VAL_MAX - val);
	} else {
		set_value(adjust_setting[mode],val);
	}
	move_cur(0,9);
	set_color(RED);
	draw_str(adjust_name[mode]);
	draw_bar(val);
	if( mode == AdjFine ) val -= FineCenter;
	move_cur(12,9);
	set_color(YELLOW);
	sprintf(str,"%3d",val);
	draw_str(str);
	volume_timer = 30;	/* wait 3 sec before clear */
}

void tv_adjust_mode() {
	adjust_mode++;
	if( adjust_mode >= NAdj ) adjust_mode = AdjBright;
	tv_adjust(adjust_mode,adjust_value[adjust_mode]);
}

void tv_switch(flag)
int flag;
{
	send_ctl(0x4);
	if(flag) {
		if( channel_mode == ModeVideo ) {
			send_data(0xd);
		} else {
			send_data(0xf);	/* turn on */
		}
	} else {
		send_data(0x0);		/* turn off */
	}
}

void raw_mode(flag)
int flag;
{
#ifdef BSD
	static struct termios save;
	struct termios t;
#else
	static struct termio save;
	struct termio t;
#endif
	if( flag ) {	/* set term to raw mode */
#ifdef BSD
		ioctl(2,TIOCGETA,&t);
#else
		ioctl(2,TCGETA,&t);
#endif
		save = t;
		t.c_lflag &= ~(ICANON|ECHO|ECHOE|ECHOK|ECHONL);
		t.c_cc[VMIN] = 0;
		t.c_cc[VTIME] = 1;	/* 0.1 sec to expire */
	} else {		/* restore */
		t = save;
	}
#ifdef BSD
	ioctl(2,TIOCSETAW,&t);
#else
	ioctl(2,TCSETAW,&t);
#endif
}

void tick() {	/* 0.1 $BIC$K0lEY(B */
	static int count = 0;
	count++;
	if( volume_timer && --volume_timer <= 0 ) flush_volume();
	if( channel_timer && --channel_timer <=0 ) clear_channel();
	if( direct_timer && --direct_timer <= 0 && direct_channel ) {
		channel[channel_mode] = direct_channel;
		direct_channel = 0;
		tv_channel(channel_mode,channel[channel_mode],-1);
	}
	if( clock_flag && count%100 == 0 ) draw_clock();
}

#define	KEY_UP		(0x100 | 'A')
#define	KEY_DOWN	(0x100 | 'B')
#define	KEY_RIGHT	(0x100 | 'C')
#define	KEY_LEFT	(0x100 | 'D')
#define	KEY_F5		0x205
#define	KEY_F6		0x207
#define	KEY_F7		0x208
#define KEY_F8		0x209
#define KEY_F9		0x20a
#define KEY_F10		0x20b
#define KEY_F11		0x20d
#define KEY_F12		0x20e
#define	KEY_PGUP	0x215
#define	KEY_PGDN	0x216
int read_key() {
	unsigned char c;
	int prev = -1;
	int done = false;
	int ret;
	while( !done ) {
		while( read(2,&c,sizeof(c)) <= 0 ) {
			tick();
			if( prev >= 0 ) {
				ret = prev;
				prev = -2;
				done = true;
				break;
			}
		}
		switch(prev) {
		  case 0x1b:
			prev = c;
			break;
		  case '[':
			if( 'A' <= c && c <= 'D' ) {
				ret = (0x100 | c);
				done = true;
#ifdef BSD
			} else if( 'Q' <= c && c <= 'V' ) {
				if( c == 'Q' ) c--;
				ret = (0x200 | c - 'K');
				done = true;
#endif
			} else if( '5' <= c && c <= '6' ) {
				prev = (0x210 | c - '0');
			} else prev = c;
			break;
		  case '1':
			if( '0' <= c && c <= '9' ) prev = (0x200 | c - '0');
			break;
		  case '2':
			if( '0' <= c && c <= '9' ) prev = (0x200 | c - '0') + 10;
			break;
		  default:
			if( (prev & 0xff00) == 0x200 && c == '~' ) {
				ret = prev;
				done = true;
			} else if( c != 0x1b ) {
				ret = c;
				done = true;
			} else {
				prev = c;
			}
		}
	}
	return ret;
}

void tv_initial() {
	static int str[] = { 0xff, 0xe0, 0xfd, 0xc0, 0xfd, 0xe9, 0xfd, 0xf0,
						 0xff, 0xc4, 0xff, 0x44, -1 };
	int i;
	for( i=AdjBright; i < NAdj; i++ ) tv_adjust(i,adjust_value[i]);
	tv_volume(volume);
	tv_channel(channel_mode,channel[channel_mode],-1);
	clear();
	send_ctl(0x5);
	send_data(0x2e);
	send_ctl(0x6);
	send_data(0x2b);
	for( i=0; str[i] >= 0; i++ ) send_char(str[i]);
}

void read_config(fp)
FILE *fp;
{
	char str[256];
	int r, i;
	do {
		r = fscanf(fp,"%s",str);
		if( !strcmp(str,"Channel:") ) {
			for( i=0; i < NMode && r > 0 ; i++ )
				r = fscanf(fp,"%d",&channel[i]);
		} else if( !strcmp(str,"Channel-Mode:") ) {
			r = fscanf(fp,"%d",&channel_mode);
		} else if( !strcmp(str,"Adjust:") ) {
			for( i=1; i < NAdj && r > 0; i++ )
				r = fscanf(fp,"%d",&adjust_value[i]);
		} else if( !strcmp(str,"Volume:") ) {
			r = fscanf(fp,"%d",&volume);
		} else if( !strcmp(str,"TV-Fine-Tune:") ) {
			for( i=0; i < *tvch_table && r > 0; i++ ) {
				if( (r=fscanf(fp,"%d",&tvch_fine[i])) > 0 )
					tvch_fine[i] += FineCenter;
			}
		} else if( !strcmp(str,"Cable-Fine-Tune:") ) {
			for( i=0; i < *cvch_table && r > 0; i++ ) {
				if( (r=fscanf(fp,"%d",&cvch_fine[i])) > 0 )
					cvch_fine[i] += FineCenter;
			}
		} else r = -1;
	} while( r > 0 );
}

void write_config(fp)
FILE *fp;
{
	int i;
	fprintf(fp,"Channel:");
	for( i=0; i < NMode; i++ ) fprintf(fp," %d",channel[i]);
	fprintf(fp,"\n");
	fprintf(fp,"Channel-Mode: %d\n",channel_mode);
	fprintf(fp,"Adjust:");
	for( i=1; i < NAdj; i++ ) fprintf(fp," %d",adjust_value[i]);
	fprintf(fp,"\n");
	fprintf(fp,"Volume: %d\n",volume);
	fprintf(fp,"TV-Fine-Tune:");
	for( i=0; i < *tvch_table; i++ ) fprintf(fp," %d",tvch_fine[i]-FineCenter);
	fprintf(fp,"\n");
	fprintf(fp,"Cable-Fine-Tune:");
	for( i=0; i < *cvch_table; i++ ) fprintf(fp," %d",cvch_fine[i]-FineCenter);
	fprintf(fp,"\n");
}

main(int argc, char *argv[]) {
	FILE *fp;
	int c;
	int i;
	int done = false;
	for( i=0; i < NChTV; i++ ) tvch_fine[i] = FineCenter;
	for( i=0; i < NChCable; i++ ) cvch_fine[i] = FineCenter;
	if( fp=fopen(TV_CONFIG,"r") ) {
		read_config(fp);
		fclose(fp);
	}
#ifdef BSD
	if( open("/dev/io",O_RDONLY) < 0 )
#else
	if( ioperm(TV_C,1,1) || ioperm(TV_D,1,1) )
#endif
	{
		fprintf(stderr,"can't get I/O permissions \n");
		exit(-1);
	}
	raw_mode(ON);
	tv_initial();
	tv_switch(ON);
	while( !done ) {
		c = read_key();
		if( '0' <= c && c <= '9' ) {
			direct_channel = direct_channel*10 + (c - '0');
			direct_timer = 7;
			continue;
		}
		direct_channel = direct_timer = 0;
		switch(c) {
		  case 'x':
		  case 'X':
			fp = fopen(TV_CONFIG,"w");
			write_config(fp);
			fclose(fp);
		  case 'D'-'@':	/* control-D */
		  case 0x1b:	/* esc */
		  case 'q':
		  case 'Q':
			mute_flag = false;
			tv_volume(0);		/* mute */
			tv_switch(OFF);
			done = true;
			break;
		  case '-':
		  case KEY_PGDN:
			tv_channel_down(channel_mode);
			break;
		  case '+':
		  case '=':
		  case KEY_PGUP:
			tv_channel_up(channel_mode);
			break;
		  case 0x7f:
			if( channel_mode == ModeTV || channel_mode == ModeCable ) {
				channel_fine[channel_mode][channel[channel_mode]]
					= -FineCenter;
			}
			break;
		  case 'c':
		  case 'C':
			channel_mode = ModeCable;
			tv_switch(ON);
			tv_channel(channel_mode,channel[channel_mode],-1);
			break;
		  case 't':
		  case 'T':
			channel_mode = ModeTV;
			tv_switch(ON);
			tv_channel(channel_mode,channel[channel_mode],-1);
			break;
		  case 'v':
		  case 'V':
			channel_mode = ModeVideo;
			tv_switch(ON);
			tv_channel(channel_mode,channel[channel_mode],-1);
			break;
		  case 'l':
			channel_mode = ModeLinear;
			tv_switch(ON);
			tv_channel(channel_mode,channel[channel_mode],-1);
			break;
		  case KEY_DOWN:
			if( volume > 0 ) volume--;
			tv_volume(volume);
			break;
		  case KEY_UP:
			clear_mute();
			mute_flag = false;
			if( volume < VAL_MAX ) volume++;
			tv_volume(volume);
			break;
		  case 'm':
		  case 'M':
			if( mute_flag ) {
				mute_flag = false;
				tv_volume(volume);
				clear_mute();
			} else {
				set_value(0x100,0);
				mute_flag = true;
				draw_mute();
			}
			break;
		  case KEY_RIGHT:
			if( adjust_value[adjust_mode] < VAL_MAX )
				adjust_value[adjust_mode]++;
			tv_adjust(adjust_mode,adjust_value[adjust_mode]);
			break;
		  case KEY_LEFT:
			if( adjust_value[adjust_mode] > 0 )
				adjust_value[adjust_mode]--;
			tv_adjust(adjust_mode,adjust_value[adjust_mode]);
			break;
		  case KEY_F6:
			clock_flag = false;
			clear();
			break;
		  case KEY_F7:
			if( clock_flag ) {
				clock_flag = false;
				clear_clock();
			} else {
				clock_flag = true;
				draw_clock();
			}
			break;
		  case KEY_F8:
			tv_adjust_mode();
			break;
		  default:
			printf(" char=0x%x\n",c);
		}
	}
	raw_mode(OFF);
}

/*
 * For Gnu Emacs.
 * Local Variables:
 * tab-width: 4
 * End:
 */

