view simpletv.c @ 8:fc60aa65d85f

Reindent with c-basic-offset = 4
author darius
date Sun, 21 Aug 2005 12:35:31 +0000
parents 210d197c77f9
children ef818ccd826f
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 = 720;
int		height = 576;
#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", lircfails);

		    /* Try and get an event */
		    if (lirc_nextcode(&code) == 0) {
			if (code == NULL) {
			    continue;
			}
			
			/* Translate it (via the config file) */
			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 {
			/* lircd probably died */
			uselirc = 0;
			fprintf(stderr, "Failed to communicate with LIRC - daemon exited?");
		    }
		} 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;
}