/*	X Window Videum Tool
 *	gcc -o xvideum -L/usr/X11R6/lib/ -lXt -lXaw -Wall xvideum.c
 *
 *	For testing the Video for Linux Two Videum driver
 *
 *	This program was written by Bill Dirks.
 *	This program is in the public domain.
 */

/*  Set these according to your set-up */
#define MY_DEVICE "/dev/video0"
#define MY_INPUT  0	       /*-> 0=MXC, 1=COMPOSITE, 2=SVIDEO */
#define MY_STD	  "PAL"        /*-> "PAL" or "NTSC" */
#define MY_WIDTH  320
#define MY_HEIGHT 240
#define MY_PIXELFORMAT PIX_FMT_RGB565
#define MY_CHROMACOLOR 0xff00ff /* magenta */
/* if you are running 8 bit color or 24 bit color, you will have to
 modify the following const; I suppose the RIGHT way to do it is
 through X calls, but I don_t know X well enough yet   patb */
#define MY_CHROMACOLOR16 0xf81f /* in rgb 5-6-5 format */
/* PIX_FMT_YUYV */
/* PIX_FMT_GREY */
/* PIX_FMT_YUV420 */
/* PIX_FMT_RGB555 */
/* PIX_FMT_RGB565 */
/* PIX_FMT_RGB24 */
/* PIX_FMT_RGB32 */

/*  Uncomment the following to test stuff  */
//#define TEST_SELECT      /* test the driver select() handling */
//#define TEST_STREAMING   /* test streaming capture */
//#define TEST_FRAMERATE	400000  /* 10million / frames-per-second */
#define TEST_INPUTS
#define TEST_FORMATS
#define TEST_CONTROLS
//#define TEST_PREVIEW

#define STREAMBUFS	4

#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/mman.h>
#include <errno.h>

/* These are needed to use the Videum driver */
#include <linux/fs.h>
#include <linux/kernel.h>
//#include <linux/videodev.h>  /* Video for Linux Two */
#include "videodev.h"

#include <X11/StringDefs.h>
#include <X11/Intrinsic.h>
#include <X11/Xaw/Command.h>
#include <X11/Xaw/Viewport.h>
#include <X11/Xaw/Porthole.h>
#include <sys/shm.h>
#include <X11/extensions/XShm.h>


int bShowReads = 1;

void MoveVidcapWindow(int vid, int x, int y)
{
    struct video_window vw = { x, y, MY_WIDTH, MY_HEIGHT, MY_CHROMACOLOR, 0, 0};
    ioctl(vid, VIDIOC_S_WIN, &vw);
}
typedef struct tag_vimage
{
	struct video_buffer	vidbuf;
	char			*data;
	XImage			*shmimage;
        XShmSegmentInfo *shminfo;
        int shmsize;
}	VIMAGE;

//void quit(Widget w, XtPointer client, XtPointer call)
//{
//	exit(0);
//}
void printctrl(struct video_queryctrl *qc)
{	
	printf("  %-12s %-6s %8d %8d %7d %8d\n",
	       qc->name,
	       (qc->step == -1) ? "(enum)" : (qc->step == 0) ? "(bool)" :
	       "(int)",
	       qc->minimum, qc->maximum, qc->step, qc->default_value);
}

int
main(int argc, char *argv[])
{
	XtAppContext		app;
	Widget			toplevel;
        Widget			read_copy_viewport;

	int			vid;
	int			err;
	int			i;
	struct video_requestbuffers req;
        struct video_format	fmt;
	struct video_parm	parm;
	struct video_standard	std;
#ifdef TEST_CONTROLS
	struct video_queryctrl	qc;
#endif
#ifdef TEST_SELECT
	fd_set			rdset;
	struct timeval		timeout;
	int			n;
#endif
	VIMAGE			vimage[STREAMBUFS];
	struct video_buffer	tempbuf;
        GC			imagegc;


/*->	Put in the device node name */
	vid = open(MY_DEVICE, O_RDONLY | O_NONBLOCK);//O_NONCAP);
	if (vid < 0)
	{
		printf("No video device\n");
		return 1;
	}

#ifdef TEST_INPUTS
	printf("Video inputs on this device:\n");
	for (i = 0, err = 0; err == 0; ++i)
	{	
		struct video_input inp;
		inp.index = i;
		err = ioctl(vid, VIDIOC_ENUMINPUT, &inp);
		if (!err)
			printf("  %d: \"%s\" is a%s, %s audio\n", i,
			       inp.name,
			       (inp.type == INPUT_TYPE_TUNER) ?
			       " TV tuner" : "n analog input",
			       (inp.capability & INPUT_CAP_AUDIO) ? 
			       "has" : "no"
				);
	}
#endif

	printf("Video standards supported:");
	for (i = 0, err = 0; err == 0; ++i)
	{
		struct video_enumstd	estd;
		estd.index = i;
		err = ioctl(vid, VIDIOC_ENUMSTD, &estd);
		if (!err)
		{
			printf("%c %s", i?',':' ', estd.std.name);
			if (strcmp(MY_STD, estd.std.name) == 0)
				std = estd.std;
		}
	}
	printf("\n");

#ifdef TEST_FORMATS
	printf("Video capture image formats supported:\n");
	for (i = 0, err = 0; err == 0; ++i)
	{
		struct video_fmtdesc	fmtd;
		fmtd.index = i;
		err = ioctl(vid, VIDIOC_G_CAPFMT, &fmtd);
		if (!err)
			printf("  %d: %s  (%s)\n", i,
			       fmtd.description,
			       (fmtd.flags & FMT_FLAG_COMPRESSED) ?
				"compressed" : "uncompressed");
	}
#endif

#ifdef TEST_CONTROLS
	printf("Controls supported:\n");
	printf("  Label        type    Minimum  Maximum    Step  Default\n");
	qc.id = CID_BRIGHTNESS;
	if (ioctl(vid, VIDIOC_QUERYCTRL, &qc) == 0)
		printctrl(&qc);
	qc.id = CID_CONTRAST;
	if (ioctl(vid, VIDIOC_QUERYCTRL, &qc) == 0)
		printctrl(&qc);
	qc.id = CID_SATURATION;
	if (ioctl(vid, VIDIOC_QUERYCTRL, &qc) == 0)
		printctrl(&qc);
	qc.id = CID_HUE;
	if (ioctl(vid, VIDIOC_QUERYCTRL, &qc) == 0)
		printctrl(&qc);
#endif

/*->	0=MXC, 1=COMPOSITE, 2=SVIDEO */
	ioctl(vid, VIDIOC_G_PARM, &parm);
	parm.input = MY_INPUT;
#ifdef TEST_FRAMERATE
	parm.capturemode |= CAP_TIMEPERFRAME;
	parm.timeperframe = TEST_FRAMERATE;
#endif
	err = ioctl(vid, VIDIOC_S_PARM, &parm);
	if (err)
		printf("S_PARM returned error %d\n",errno);

/*->	VIDEO_STD_PAL or VIDEO_STD_NTSC */
	err = ioctl(vid, VIDIOC_S_STD, &std);
	if (err)
		printf("S_STD returned error %d\n",errno);

/*->	Image width, height, pixel format */
	ioctl(vid, VIDIOC_G_FMT, &fmt);
	fmt.width = MY_WIDTH;
	fmt.height = MY_HEIGHT;
	fmt.pixelformat = MY_PIXELFORMAT;
	err = ioctl(vid, VIDIOC_S_FMT, &fmt);
	if (err)
	{
		printf("S_FMT returned error %d\n",errno);
		return 1;
	}

        printf("S_FMT worked; we're at w/h %d/%d totalbytes %d\n",
               fmt.width, fmt.height, fmt.sizeimage);

#ifdef TEST_PREVIEW
        {
            struct video_framebuffer vf;
            ioctl(vid, VIDIOC_G_FBUF, &vf);
            vf.flags = FBUF_FLAG_CHROMAKEY | FBUF_FLAG_OVERLAY;
            printf("chromakey set; video capture buffer at %p\n",vf.base[0]);
            ioctl(vid, VIDIOC_S_FBUF, &vf);
            ioctl(vid, VIDIOC_PREVIEW, 1);
        }
#endif  // TEST_PREVIEW
        
        req.count = 1;
#ifdef TEST_STREAMING
	req.count = STREAMBUFS;
	req.type = BUF_TYPE_CAPTURE;
	err = ioctl(vid, VIDIOC_REQBUFS, &req);
	if (err < 0 || req.count < 1)
	{
		printf("REQBUFS returned error %d, count %d\n",
		       errno,req.count);
		return 1;
	}
	for (i = 0; i < req.count; ++i)
	{
		vimage[i].vidbuf.index = i;
		vimage[i].vidbuf.type = BUF_TYPE_CAPTURE;
		err = ioctl(vid, VIDIOC_QUERYBUF, &vimage[i].vidbuf);
		if (err < 0)
		{
			printf("QUERYBUF returned error %d\n",errno);
			return 1;
		}
		vimage[i].data = mmap(0, vimage[i].vidbuf.length, PROT_READ,
				      MAP_PRIVATE, vid, 
				      vimage[i].vidbuf.offset);
		if ((int)vimage[i].data == -1)
		{
			printf("mmap() returned error %d\n", errno);
			return 1;
		}
	}
#else
	vimage[0].data = malloc(fmt.sizeimage);
	if (vimage[0].data == NULL)
	{
		printf("malloc(%d) failed\n", fmt.sizeimage);
		return 1;
	}
#endif

	toplevel = XtAppInitialize(&app, "xvideum", NULL, 0,
				   &argc, argv, NULL, NULL, 0);


        /* I create a window that is twice as wide as we need;
         the read() data goes into the left hand size;
         the live image will be mapped into the right hand side */
        {
            XColor xc = {MY_CHROMACOLOR,
            0x8000, 0x8000, 0x8000, 255};
            
	read_copy_viewport = XtVaCreateManagedWidget("port",
	            portholeWidgetClass, toplevel,
                    XtNwidth, fmt.width,
                    XtNheight, fmt.height,
                    XtNbackground, MY_CHROMACOLOR16,
                    NULL);
        }
        XtRealizeWidget(toplevel);

	imagegc = XCreateGC(XtDisplay(read_copy_viewport),
                            DefaultRootWindow(XtDisplay(read_copy_viewport)),
                            0, NULL);

	for (i = 0; i < req.count; ++i)
	{
	  
		vimage[i].shminfo = (XShmSegmentInfo*) malloc (sizeof(XShmSegmentInfo));

    /* allocate shared image memory for the sub perview window */

		vimage[i].shmimage = XShmCreateImage(
			XtDisplay(read_copy_viewport),
			DefaultVisual(XtDisplay(read_copy_viewport), 0),
			fmt.depth, ZPixmap,
			NULL,vimage[i].shminfo,
			fmt.width, fmt.height);
		vimage[i].shmsize = vimage[i].shmimage->bytes_per_line 
		  * vimage[i].shmimage->height;
		if ((err = (vimage[i].shminfo->shmid 
			    = shmget(IPC_PRIVATE,  
				     vimage[i].shmsize,
				     IPC_CREAT | 0777))) == -1) 
	  {
	    fprintf(stderr,"Shared memory error (shmget) - shmget returned %d",err);
	    return(1);
	}
	if ((vimage[i].shminfo->shmaddr = 
	     (char *) shmat(vimage[i].shminfo->shmid, (void *)0, 0))
	    == (char *)(-1)) {
	  fprintf(stderr,"Shared memory error (shmget) no data");
	  return(1);
	}
	vimage[i].shminfo->readOnly=False;
	vimage[tempbuf.index].shmimage->data = vimage[i].shminfo->shmaddr;
	if(XShmAttach(XtDisplay(read_copy_viewport), vimage[i].shminfo) ==0)
	  {
	    printf("XShm Attach failed, aborting\n");
	    return 1;
	  };
	/*		if (vimage[i].ximage == NULL)
		{
			printf("No XImage\n");
			return 1;
		}
		*/
        }

#ifdef TEST_STREAMING
	for (i = 0; i < req.count; ++i)
		if ((err = ioctl(vid, VIDIOC_QBUF, vimage[i].vidbuf.type)))
		{
			printf("QBUF returned error %d\n",errno);
			return 1;
		}
	err = ioctl(vid, VIDIOC_STREAMON, vimage[0].vidbuf.type);
	if (err)
		printf("STREAMON returned error %d\n",errno);
	tempbuf.type = vimage[0].vidbuf.type;
#endif

	for (;;)
	{
		XEvent event;

		err = 0;
		if (XtIsRealized(read_copy_viewport))
		{
#ifdef TEST_SELECT
			FD_ZERO(&rdset);
			FD_SET(vid, &rdset);
			timeout.tv_sec = 1;
                        timeout.tv_usec = 0;
			n = select(vid + 1, &rdset, NULL, NULL, &timeout)
                        err = -1;
			if (n == -1)
				fprintf(stderr, "select error.\n");
			else if (n == 0)
				fprintf(stderr, "select timeout\n");
			else if (FD_ISSET(vid, &rdset))
                            err = 0;
#else
#ifdef TEST_STREAMING
			err = ioctl(vid, VIDIOC_NEXTBUF, 0);
			if (err)
				printf("NEXTBUF returned error %d\n",errno);
#endif
#endif
			if (err == 0)
			{
#ifdef TEST_STREAMING
				tempbuf.type = vimage[0].vidbuf.type;
				err = ioctl(vid, VIDIOC_DQBUF, &tempbuf);
				if (err)
					printf("DQBUF returned error %d\n",
					       errno);
#else
                                if (bShowReads)
                                    read(vid, vimage[tempbuf.index].shmimage->data, fmt.sizeimage);
#endif
				XShmPutImage(XtDisplay(read_copy_viewport),
					  XtWindow(read_copy_viewport),
					  imagegc,
					  vimage[tempbuf.index].shmimage,
					  0, 0,
					  0, 0,
					  fmt.width, fmt.height,1);
					XSync(XtDisplay(read_copy_viewport), 0);
#ifdef TEST_STREAMING
				err = ioctl(vid, VIDIOC_QBUF, 
					    vimage[0].vidbuf.type);
				if (err)
					printf("QBUF returned error %d\n",
					       errno);
#endif
			}
		}

		while (XtAppPending(app))
		{
			XtAppNextEvent(app, &event);
			switch (event.type)
                        {
#ifdef TEST_PREVIEW
                        case ConfigureNotify:
                            {
                                XConfigureEvent *ce = (XConfigureEvent*)&event;
                                MoveVidcapWindow(vid,ce->x+MY_WIDTH,ce->y);
                            }
                            break;
#endif // TEST_PREVIEW
                        case FocusIn:
                            bShowReads = 1;
                            break;
                        case FocusOut:
                            bShowReads = 0;
                            break;
                        default:
				break;
			}
			XtDispatchEvent(&event);
		}
        }
        close(vid);
	return 0;
}

