view simpletv.c @ 1:769b155a34f9 SIMPETV_0_1

Initial revision
author darius
date Tue, 04 Jan 2005 05:17:43 +0000
parents
children 8d7d1680db7d
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 <machine/ioctl_bt848.h>
#include <machine/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_12;*/
    geo.oformat = METEOR_GEO_YUV_422 | METEOR_GEO_YUV_12;
    /* geo.oformat = 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;

    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);
    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_im	age = 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);
#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
#  warning How do we destroy an XvImage?
#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;
}