view simpletv.c @ 6:f0cbbe964629

XShmDetach() both cases.
author darius
date Tue, 28 Jun 2005 13:14:03 +0000
parents 8b6b46a1261d
children 210d197c77f9
line wrap: on
line source

/* Capture and store a 24 bit RGB image as a PPM file */
/* Copyright (c) 2000  Randall Hopper
 *
 * Based on a grab.c from Roger Hardiman, which was
 * based on an original program by Mark Tinguely and Jim Lowe .
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Mark Tinguely and Jim Lowe
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <poll.h>
#include <sys/mman.h>
#include <sys/fcntl.h>
#include <sys/time.h>
#include <libgen.h>
#include <sys/soundcard.h>
#include <dev/bktr/ioctl_bt848.h>
#include <dev/bktr/ioctl_meteor.h>
#include <machine/param.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XShm.h>
#include <X11/extensions/Xvlib.h>
#ifndef  USE_XVIMAGES
#include "Hermes/Hermes.h"
#endif

#include <lirc/lirc_client.h>

/* Some BT848/BT878 cards have gain control hardware which takes
   1 or 2 seconds to settle. If you get a washed out / too bright
   image, change the GRABBER_SETTLE_TIME from 0 to either 1 or 2
*/
/*#define GRABBER_SETTLE_TIME 10*/
#define GRABBER_SETTLE_TIME 0


#define PAL 1
#define NTSC 2

/* PAL is 768 x 576. NTSC is 640 x 480 */
#define PAL_HEIGHT 576
#define NTSC_HEIGHT 480

int		bktr_fd;
int		tuner_fd;
unsigned char  *bktr_buffer;
#if 1
int		width = 320;
int		height = 240;
#else
int		width = 640;
int		height = 480;
#endif

#ifndef USE_XVIMAGES
static XImage  *rgb_image;
static Pixmap	pmap;
#endif
static XShmSegmentInfo shminfo;
static Display *disp;
static Window	win;
static Visual  *vis;
static int	scr;
static GC	gc;
static int	depth;
static Colormap	cmap;
static XVisualInfo *vi;
static int	bits_per_pixel;
static XvImage *yuv_image;
static XvAdaptorInfo *xv_adaptors;
static int	xv_num_adaptors;
static int	xv_adaptor;
static int	xv_format_id;

static int	channel;

#define DO_IOCTL_GERR(str)     fprintf(stderr, "ioctl(%s) failed: %s\n", \
                                       str, strerror(errno) )
#define DO_IOCTL_SERR(str,arg) fprintf(stderr, "ioctl(%s, %ld) failed: %s\n",\
                                       str, (long)arg, strerror(errno) )
/*--------------------------------------------------------------------------*/

void
Close() {
	close(tuner_fd);
	close(bktr_fd);
}

void
Open() {
	struct meteor_geomet geo;
	int		buffer_size, format, source, c;
	char           *device_name;

	format = PAL;		/* default value */
	source = 1;		/* default value */
	device_name = "/dev/bktr0";	/* default value */

	/* Open the Meteor or Bt848/Bt878 grabber */
	if ((bktr_fd = open(device_name, O_RDONLY)) < 0) {
		printf("open failed: %d\n", errno);
		exit(1);
	}
	if ((tuner_fd = open("/dev/tuner0", O_RDONLY)) < 0) {
		printf("open failed: %d\n", errno);
		exit(1);
	}
	/* set up the capture type and size */
	geo.rows = height;
	geo.columns = width;
	geo.frames = 1;
#ifdef USE_XVIMAGES
	/* Should be YUV_12, but 422 actually gives a synced picture.  Though  */
	geo.oformat = METEOR_GEO_YUV_422 | METEOR_GEO_YUV_12;
#else
	geo.oformat = METEOR_GEO_RGB24;
#endif

	/* switch from interlaced capture to single field capture if */
	/* the grab height is less than half the normal TV height */
	/* this gives better quality captures when the object in the TV */
	/* picture is moving */
	if ((format == PAL) && (height <= (PAL_HEIGHT / 2)))
		geo.oformat |= METEOR_GEO_ODD_ONLY;
	if ((format == NTSC) && (height <= (NTSC_HEIGHT / 2)))
		geo.oformat |= METEOR_GEO_ODD_ONLY;

	if (ioctl(bktr_fd, METEORSETGEO, &geo) < 0) {
		printf("METEORSETGEO ioctl failed: %d\n", errno);
		exit(1);
	}
	/* Select PAL or NTSC */
	switch (format) {
	case PAL:
		c = METEOR_FMT_PAL;
		break;
	case NTSC:
		c = METEOR_FMT_NTSC;
		break;
	default:
		c = METEOR_FMT_NTSC;
		break;
	}

	c = BT848_IFORM_F_PALBDGHI;
	if (ioctl(bktr_fd, BT848SFMT, &c) < 0) {
		DO_IOCTL_SERR("BT848SFMT", c);
		return;
	}
	c = AUDIO_TUNER;
	if (ioctl(tuner_fd, BT848_SAUDIO, &c) < 0) {
		DO_IOCTL_SERR("BT848SFMT", c);
		return;
	}
	c = CHNLSET_AUSTRALIA;
	if (ioctl(tuner_fd, TVTUNER_SETTYPE, &c) < 0) {
		DO_IOCTL_SERR("TVTUNER_SETTYPE", c);
		return;
	}
	if (ioctl(tuner_fd, TVTUNER_SETCHNL, &channel) < 0) {
		DO_IOCTL_SERR("TVTUNER_SETCHNL", channel);
		return;
	}
	/* Select the Video Source */
	/* Video In, Tuner, S-Video */
	switch (source) {
	case 0:
		c = METEOR_INPUT_DEV0;
		break;
	case 1:
		c = METEOR_INPUT_DEV1;
		break;
	case 2:
		c = METEOR_INPUT_DEV2;
		break;
	case 3:
		c = METEOR_INPUT_DEV3;
		break;
	default:
		c = METEOR_INPUT_DEV0;
		break;
	}

	printf("Input - %x\n", c);
	if (ioctl(bktr_fd, METEORSINPUT, &c) < 0) {
		printf("ioctl failed: %d\n", errno);
		exit(1);
	}
	/* Use mmap to Map the drivers grab buffer */
	buffer_size = width * height * 4;	/* R,G,B,spare */
	bktr_buffer = (unsigned char *)mmap((caddr_t) 0, buffer_size, PROT_READ,
	    MAP_SHARED, bktr_fd, (off_t) 0);

	if (bktr_buffer == (unsigned char *)MAP_FAILED)
		exit(1);


	/* We may need to wait for a short time to allow the grabber */
	/* brightness to settle down */
	sleep(GRABBER_SETTLE_TIME);
}

/*--------------------------------------------------------------------------*/

void
Capture() {
	int		c;

	/* Perform a single frame capture */
	c = METEOR_CAP_SINGLE;
	ioctl(bktr_fd, METEORCAPTUR, &c);
}

/*--------------------------------------------------------------------------*/

void
SaveImage() {
	unsigned char  *line_buffer;
	int		o         , w, h;
	unsigned char  *p;
	unsigned char	header[30];
	char           *filename = "t.ppm" /* argv[3] */ ;

	/* Create the output file */
	if ((o = open(filename, O_WRONLY | O_CREAT, 0644)) < 0) {
		printf("ppm open failed: %d\n", errno);
		exit(1);
	}
	/* make PPM header and save to file */
	sprintf(header, "P6\n%d\n%d\n255\n", width, height);
	write(o, header, strlen(header));

	/* save the RGB data to PPM file */
	/* save this one line at a time */
	line_buffer = (unsigned char *)malloc(width * 3 * sizeof(unsigned char));
	p = bktr_buffer;
	for (h = 0; h < height; h++) {
		for (w = 0; w < width; w++) {
			line_buffer[(w * 3) + 2] = *p++;	/* blue */
			line_buffer[(w * 3) + 1] = *p++;	/* green */
			line_buffer[(w * 3) + 0] = *p++;	/* red */
			p++;	/* NULL byte */
		}
		write(o, line_buffer, width * 3);
	}
	close(o);
	free(line_buffer);
}

void
X_ShowCursor(void) {
	XDefineCursor(disp, win, 0);
}

void
X_HideCursor(void) {
	Cursor		no_ptr;
	Pixmap		bm_no;
	XColor		black  , dummy;
	Colormap	colormap;
	static unsigned char bm_no_data[] = {0, 0, 0, 0, 0, 0, 0, 0};

	colormap = DefaultColormap(disp, DefaultScreen(disp));
	XAllocNamedColor(disp, colormap, "black", &black, &dummy);
	bm_no = XCreateBitmapFromData(disp, win, bm_no_data, 8, 8);
	no_ptr = XCreatePixmapCursor(disp, bm_no, bm_no, &black, &black, 0, 0);

	XDefineCursor(disp, win, no_ptr);
	XFreeCursor(disp, no_ptr);
	if (bm_no != None)
		XFreePixmap(disp, bm_no);

}

/*
 * Sends the EWMH fullscreen state event.
 *
 * action: could be on of _NET_WM_STATE_REMOVE -- remove state
 *                        _NET_WM_STATE_ADD    -- add state
 *                        _NET_WM_STATE_TOGGLE -- toggle
 */
#define _NET_WM_STATE_REMOVE        0	/* remove/unset property */
#define _NET_WM_STATE_ADD           1	/* add/set property */
#define _NET_WM_STATE_TOGGLE        2	/* toggle property  */
void
vo_x11_ewmh_fullscreen(int action) {
	XEvent		xev;

	assert(action == _NET_WM_STATE_REMOVE ||
	    action == _NET_WM_STATE_ADD || action == _NET_WM_STATE_TOGGLE);


	/* init X event structure for _NET_WM_FULLSCREEN client msg */
	xev.xclient.type = ClientMessage;
	xev.xclient.serial = 0;
	xev.xclient.send_event = True;
	xev.xclient.message_type = XInternAtom(disp, "_NET_WM_STATE", False);
	xev.xclient.window = win;
	xev.xclient.format = 32;
	xev.xclient.data.l[0] = action;
	xev.xclient.data.l[1] = XInternAtom(disp, "_NET_WM_STATE_FULLSCREEN", False);
	xev.xclient.data.l[2] = 0;
	xev.xclient.data.l[3] = 0;
	xev.xclient.data.l[4] = 0;

	/* finally send that damn thing */
	if (!XSendEvent(disp, DefaultRootWindow(disp), False,
	    SubstructureRedirectMask | SubstructureNotifyMask,
	    &xev)) {
		fprintf(stderr, "Failed to send fullscreen command\n");
	}
}


void
X_Setup(int w, int h) {
	XGCValues	gcvals;
	Window		root;
	XVisualInfo	vinfo_pref;
	int		num_vis;
	XPixmapFormatValues *pf;
	int		num_pf    , pfi, i, j;
	XSizeHints	sz_hint;
	
	disp = XOpenDisplay(NULL);
	if (!disp) {
		fprintf(stderr, "X-Error: unable to connect to display\n");
		exit(1);
	}
	XSynchronize(disp, True);

	scr = DefaultScreen(disp);
	vis = DefaultVisual(disp, scr);
	root = DefaultRootWindow(disp);
	depth = DefaultDepth(disp, scr);

	vinfo_pref.screen = scr;
	vinfo_pref.visualid = XVisualIDFromVisual(vis);
	vi = XGetVisualInfo(disp, VisualScreenMask | VisualIDMask,
	    &vinfo_pref, &num_vis);
	assert(num_vis == 1);

	win = XCreateSimpleWindow(disp, root, 0, 0, w, h, 0, 0, 0);
	gc = XCreateGC(disp, win, (unsigned long)0, &gcvals);
	XSetForeground(disp, gc, 0);
	XSetBackground(disp, gc, 1);

	XMapWindow(disp, win);
	cmap = DefaultColormap(disp, scr);

	sz_hint.flags = PAspect;
	sz_hint.min_aspect.x = width;
	sz_hint.min_aspect.y = height;
	sz_hint.max_aspect.x = width;
	sz_hint.max_aspect.y = height;

	/* set min height/width to 4 to avoid off by one errors */
	sz_hint.min_width = sz_hint.min_height = 4;
	sz_hint.flags |= PMinSize;
	XSetWMNormalHints(disp, win, &sz_hint);

	XSync(disp, False);

	/* Setup with Xv extension  */
	xv_adaptor = -1;
	xv_format_id = -1;
	XvQueryAdaptors(disp, root, &xv_num_adaptors, &xv_adaptors);
	for (i = 0; i < xv_num_adaptors; i++) {
		XvAdaptorInfo  *adaptor = &xv_adaptors[i];
		int		takes_images;

		takes_images = adaptor->type & (XvInputMask | XvImageMask);
		if (takes_images) {
			XvImageFormatValues *formats;
			int		num_formats;

			formats = XvListImageFormats(disp, adaptor->base_id, &num_formats);
			for (j = 0; j < num_formats; j++)
				if (formats[j].type == XvYUV && formats[j].format == XvPlanar &&
				    strcmp(formats[j].guid, "YV12") == 0)
					break;
			if (j < num_formats) {
				xv_adaptor = i;
				xv_format_id = formats[j].id;
				break;
			}
		}
	}
	assert(xv_adaptor >= 0);

	/* Create an image to captured frames  */
#ifdef USE_XVIMAGES
	yuv_image = XvShmCreateImage(disp, xv_adaptors[xv_adaptor].base_id,
					       xv_format_id, 0, w, h, &shminfo);
	if (!yuv_image)
		fprintf(stderr, "X-Error: unable to create XvShm XImage\n");

	shminfo.shmid = shmget(IPC_PRIVATE, yuv_image->data_size, IPC_CREAT | 0777);
	if (shminfo.shmid == -1)
		fprintf(stderr, "SharedMemory Error: unable to get identifier\n");

	shminfo.shmaddr = yuv_image->data = shmat(shminfo.shmid, 0, 0);
	yuv_image->data = shminfo.shmaddr;
#else
	rgb_image = XShmCreateImage(disp, vis, depth, ZPixmap, NULL, &shminfo, w, h);
	if (!rgb_image)
		fprintf(stderr, "X-Error: unable to create XShm XImage\n");

	shminfo.shmid = shmget(IPC_PRIVATE,
	    rgb_image->bytes_per_line * rgb_image->height,
	    IPC_CREAT | 0777);
	if (shminfo.shmid == -1)
		fprintf(stderr, "SharedMemory Error: unable to get identifier\n");

	shminfo.shmaddr = rgb_image->data = shmat(shminfo.shmid, 0, 0);
#endif

	if (!XShmAttach(disp, &shminfo))
		fprintf(stderr, "X-Error: unable to attach XShm Shared Memory Segment\n");

	/* Create a pixmap for the window background  */
#ifdef OLD
	pmap = XShmCreatePixmap(disp, win, shminfo.shmaddr, &shminfo, w, h, depth);
	if (!pmap)
		fprintf(stderr, "Unable to create pixmap\n");
#endif

	/* Determine bits-per-pixel for pixmaps  */
	pf = XListPixmapFormats(disp, &num_pf);
	assert(pf);
	for (pfi = 0; pfi < num_pf; pfi++)
		if (pf[pfi].depth == vi->depth)
			break;
	assert(pfi < num_pf);
	bits_per_pixel = pf[pfi].bits_per_pixel;
	XFree(pf);

#ifdef OLD
	XSetWindowBackgroundPixmap(disp, win, pmap);
#endif
}

/*--------------------------------------------------------------------------*/

void
X_Shutdown() {
	XShmDetach(disp, &shminfo);
#ifdef USE_XVIMAGES
	shmctl(shminfo.shmid, IPC_RMID, 0);
	shmdt(shminfo.shmaddr);
	XFree(yuv_image);
#else
	XDestroyImage(rgb_image);
#endif
	shmdt(shminfo.shmaddr);
	shmctl(shminfo.shmid, IPC_RMID, 0);
}


/*--------------------------------------------------------------------------*/

void
X_Display(void) {
	int		_w        , _h, _d;
	Window		_dw;

#ifdef USE_XVIMAGES
	XGetGeometry(disp, win, &_dw, &_d, &_d, &_w, &_h, &_d, &_d);
	XvShmPutImage(disp, xv_adaptors[xv_adaptor].base_id, win,
	    gc, yuv_image, 0, 0, yuv_image->width, yuv_image->height,
	    0, 0, _w, _h, True);
#else
	XShmPutImage(disp, win, gc, rgb_image, 0, 0, 0, 0,
	    rgb_image->width, rgb_image->height, False);
#endif
	XSync(disp, False);
}

#define CMD_NONE	0
#define CMD_CHNUP	1
#define CMD_CHNDN	2
#define CMD_MUTE	3
#define CMD_QUIT	4
#define CMD_RELOAD	5
#define CMD_CURSOR	6
#define CMD_VOLDN	7
#define CMD_VOLUP	8
#define CMD_FSTOGGLE	9

/*--------------------------------------------------------------------------*/

int
main(int argc, char *argv[]) {
#ifndef USE_XVIMAGES
	HermesHandle	conv;
	HermesFormat	fmt_source, fmt_dest;
#endif
	XEvent		e;
	char           *scratch_buf, *code, *c, *mixerdev;
	int		frames    , channelidx, oldchan, mute, cursor, fd, ret, cmd;
	int		exitnow   , mfd, uselirc, curvol;
	struct timeval	then, now, diff, lastmove;
	float		rate;
	KeySym		key;
	char		text      [255];
	int		channellist[] = {2, 7, 9, 10, 28};
	struct lirc_config *config;
	struct pollfd	fds[1];

#define NUMCHANS (sizeof(channellist) / sizeof(channellist[0]))

	channelidx = mute = cursor = 0;

	oldchan = channel = channellist[channelidx];

#ifndef USE_XVIMAGES
	if (!Hermes_Init()) {
		printf("Couldn't initialise Hermes!\n");
		exit(1);
	}
	conv = Hermes_ConverterInstance(HERMES_CONVERT_NORMAL);
	if (!conv) {
		printf("Could not obtain converter instance from Hermes!\n");
		exit(1);
	}
#endif

	X_Setup(width, height);
	XSelectInput(disp, win, KeyReleaseMask | PointerMotionMask | ButtonPressMask | ButtonReleaseMask);

	X_HideCursor();

	/* Open Capture device  */
	Open();

	/* Open audio mixer */
	mixerdev = "/dev/mixer";
	if ((mfd = open(mixerdev, O_RDWR)) == -1)
		fprintf(stderr, "Unable to open %s - %s\n", mixerdev, strerror(errno));

	/* Talk to LIRC */
	if ((fd = lirc_init(basename(argv[0]), 1)) == -1) {
		fprintf(stderr, "Unable to init lirc client library\n");
		uselirc = 0;
	} else {
		if (lirc_readconfig(NULL, &config, NULL) != 0) {
			fprintf(stderr, "Unable to parse config file\n");
			uselirc = 0;
		} else
			uselirc = 1;
		fds[0].fd = fd;
		fds[0].events = POLLRDNORM;
	}

#ifndef USE_XVIMAGES
	/* Conversion from and to formats  */
	fmt_source.indexed = 0;
	fmt_source.bits = 32;
	fmt_source.r = 0xff0000;
	fmt_source.g = 0x00ff00;
	fmt_source.b = 0x0000ff;
	fmt_source.a = 0;

	fmt_dest.indexed = 0;
	fmt_dest.bits = bits_per_pixel;
	fmt_dest.r = vi->red_mask;
	fmt_dest.g = vi->green_mask;
	fmt_dest.b = vi->blue_mask;
	fmt_dest.a = 0;
#else
	scratch_buf = malloc(width * height);
#endif

	frames = 0;
	gettimeofday(&then, NULL);
	gettimeofday(&lastmove, NULL);

	exitnow = 0;

	/* Capture loop  */
	while (1) {
		if (frames == 50) {
			gettimeofday(&now, NULL);
			timersub(&now, &then, &diff);

			rate = (float)frames / (float)(diff.tv_usec / 1000000.0 + diff.tv_sec);

			printf("%d frames in %.2f seconds, rate %.2f\n", frames, (float)(diff.tv_usec / 1000000.0) + diff.tv_sec, rate);
			gettimeofday(&then, NULL);
			frames = 0;
			XResetScreenSaver(disp);
		}
		frames++;

		timersub(&now, &lastmove, &diff);
		if (((float)diff.tv_usec / 1000000.0 + (float)diff.tv_sec) > 2.0) {
			if (cursor) {
				X_HideCursor();
				cursor = 0;
			}
		} else {
			if (!cursor) {
				X_ShowCursor();
				cursor = 1;
			}
		}

		if (XCheckMaskEvent(disp, PointerMotionMask, &e) && e.type == MotionNotify) {
			gettimeofday(&lastmove, NULL);
		}
		cmd = CMD_NONE;

		if (XCheckMaskEvent(disp, ButtonReleaseMask, &e)) {
			printf("e.type = %d\n", e.type);
			cmd = CMD_MUTE;
		}
		if (XCheckMaskEvent(disp, KeyReleaseMask, &e) && e.type == KeyRelease) {
			gettimeofday(&lastmove, NULL);

			XLookupString(&e.xkey, text, 255, &key, 0);
			printf("Press - %c\n", text[0]);

			switch (text[0]) {
			case 'q':
				cmd = CMD_QUIT;
				break;

			case '=':
			case '+':
				cmd = CMD_CHNUP;
				break;

			case '-':
				cmd = CMD_CHNDN;
				break;

			case 'h':
				cmd = CMD_CURSOR;
				break;

			case 'm':
				cmd = CMD_MUTE;
				break;

			case 'r':
				cmd = CMD_RELOAD;
				break;

			case ',':
				cmd = CMD_VOLDN;
				break;

			case '.':
				cmd = CMD_VOLUP;
				break;

			case 'f':
				cmd = CMD_FSTOGGLE;
				break;
			}
		}
		/* Poll for IR events */
		if (uselirc) {
			fds[0].revents = 0;

			while (1) {
				if (poll(fds, 1, 0) == -1) {
					fprintf(stderr, "Poll failed - %s\n", strerror(errno));
					exit(EXIT_FAILURE);
				}
				if ((fds[0].revents & POLLRDNORM) != 0) {
					fprintf(stderr, "Processing IR..\n");

					if (lirc_nextcode(&code) == 0) {
						if (code == NULL)
							continue;

						while ((ret = lirc_code2char(config, code, &c)) == 0 &&
						    c != NULL) {
							fprintf(stderr, "Got command \"%s\"\n", c);

							if (!strcmp(c, "Mute"))
								cmd = CMD_MUTE;
							else if (!strcmp(c, "CH_UP"))
								cmd = CMD_CHNUP;
							else if (!strcmp(c, "CH_DOWN"))
								cmd = CMD_CHNDN;
							else if (!strcmp(c, "Power"))
								cmd = CMD_QUIT;
							else if (!strcmp(c, "VOL_UP"))
								cmd = CMD_VOLUP;
							else if (!strcmp(c, "VOL_DOWN"))
								cmd = CMD_VOLDN;
							else if (!strcmp(c, "AV/TV"))
								cmd = CMD_FSTOGGLE;

							free(code);
						}
					}
				} else
					break;
			}
		}
		switch (cmd) {
		case CMD_QUIT:
			exitnow = 1;
			break;

		case CMD_CHNUP:
		case CMD_CHNDN:
			if (cmd == CMD_CHNUP)
				channelidx++;
			else
				channelidx--;

			if (channelidx < 0)
				channelidx = NUMCHANS - 1;
			if (channelidx > NUMCHANS - 1)
				channelidx = 0;

			channel = channellist[channelidx];

			printf("Channel - %d\n", channel);
			if (oldchan != channel) {
				oldchan = channel;

				if (ioctl(tuner_fd, TVTUNER_SETCHNL, &channel) < 0) {
					DO_IOCTL_SERR("TVTUNER_SETCHNL", channel);
					exit(1);
				}
			}
			break;

		case CMD_MUTE:
			if (mute)
				mute = 0;
			else
				mute = 1;

			printf("Mute - %d\n", mute);
			if (ioctl(tuner_fd, BT848_SAUDIO, &mute) < 0) {
				DO_IOCTL_SERR("BT848_SAUDIO", mute);
				exit(1);
			}
			break;

		case CMD_CURSOR:
			if (cursor) {
				printf("Cursor hidden\n");
				X_HideCursor();
				cursor = 0;
			} else {
				printf("Cursor revealed\n");
				X_ShowCursor();
				cursor = 1;
			}
			break;

		case CMD_RELOAD:
			printf("Reloading\n");
			Close();
			Open();
			break;

		case CMD_VOLUP:
		case CMD_VOLDN:
			if (ioctl(mfd, MIXER_READ(SOUND_MIXER_VOLUME), &curvol) == -1) {
				fprintf(stderr, "Unable to read current volume - %s\n", strerror(errno));
				break;
			}
			curvol = curvol & 0x7f;

			if (cmd == CMD_VOLUP)
				curvol += 5;
			else
				curvol -= 5;

			if (curvol < 0)
				curvol = 0;
			if (curvol > 100)
				curvol = 100;

			printf("Setting volume to %d\n", curvol);
			curvol |= curvol << 8;

			if (ioctl(mfd, MIXER_WRITE(SOUND_MIXER_VOLUME), &curvol) == -1) {
				fprintf(stderr, "Unable to write volume - %s\n", strerror(errno));
				break;
			}
			break;

		case CMD_FSTOGGLE:
			vo_x11_ewmh_fullscreen(_NET_WM_STATE_TOGGLE);
			break;

		}

		Capture();

#ifdef USE_XVIMAGES
		/* bktr's YUV_12 is planar W*H bytes Y, W/2*H/2 bytes U,  */
		/* W/2*H/2 bytes V.  Xv's YV12 is the same with U and V  */
		/* planes reversed.                                      */
		{
			int		y_off     , u_off, v_off;
			y_off = 0;
			u_off = width * height;
			v_off = u_off + width * height / 4;

			assert(yuv_image->data_size == width * height * 3 / 2);
			memcpy(yuv_image->data, bktr_buffer, u_off - y_off);
			memcpy(yuv_image->data + u_off, bktr_buffer + v_off, v_off - u_off);
			memcpy(yuv_image->data + v_off, bktr_buffer + u_off, v_off - u_off);
		}
#else
		/* SaveImage(); */

		Hermes_ConverterRequest(conv, &fmt_source, &fmt_dest);
		Hermes_ConverterCopy(conv, bktr_buffer, 0, 0, width, height, width * 4,
		    rgb_image->data, 0, 0, width, height,
		    rgb_image->bytes_per_line);
#endif

		X_Display();
		if (exitnow) {
			printf("quitting\n");
			break;
		}
	}

#ifndef USE_XVIMAGES
	Hermes_ConverterReturn(conv);
	Hermes_Done();
#endif

	X_Shutdown();
	lirc_freeconfig(config);
	lirc_deinit();

	return 0;
}