/*
 * $Id: testhiddev.c,v 1.2 2006/10/03 22:41:35 flavio Exp $
 *
 * Hacking with my USB HID monitor
 * Copyright (C) 2001 Flavio Stanchina <flavio.stanchina@tin.it>
 *
 * vi:ts=4
 */

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or 
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/types.h>
#include <linux/hiddev.h>
#include <sys/ioctl.h>

#define HIDMON_DEGAUSS    0x01 // write-only
#define HIDMON_BRIGHTNESS 0x10
#define HIDMON_CONTRAST   0x12

struct controlData { int value; const char *name; };

struct controlData controls82[] =
{
	// contiguous, read/write
	{ 0x10, "Brightness" },
	{ 0x12, "Contrast" },
	{ 0x16, "Red Video Gain" },
	{ 0x18, "Green Video Gain" },
	{ 0x1A, "Blue Video Gain" },
	{ 0x1C, "Focus" },
	{ 0x20, "Horizontal Position" },
	{ 0x22, "Horizontal Size" },
	{ 0x24, "Horizontal Pincushion" },
	{ 0x26, "Horizontal Pincushion Balance" },
	{ 0x28, "Horizontal Misconvergence" },
	{ 0x2A, "Horizontal Linearity" },
	{ 0x2C, "Horizontal Linearity Balance" },
	{ 0x30, "Vertical Position" },
	{ 0x32, "Vertical Size" },
	{ 0x34, "Vertical Pincushion" },
	{ 0x36, "Vertical Pincushion Balance" },
	{ 0x38, "Vertical Misconvergence" },
	{ 0x3A, "Vertical Linearity" },
	{ 0x3C, "Vertical Linearity Balance" },
	{ 0x40, "Parallelogram Distortion (Key Balance)" },
	{ 0x42, "Trapezoidal Distortion (Key)" },
	{ 0x44, "Tilt (Rotation)" },
	{ 0x46, "Top Corner Distortion Control" },
	{ 0x48, "Top Corner Distortion Balance" },
	{ 0x4A, "Bottom Corner Distortion Control" },
	{ 0x4C, "Bottom Corner Distortion Balance" },
	{ 0x56, "Horizontal Moiré" },
	{ 0x58, "Vertical Moiré" },
	{ 0x6C, "Red Video Black Level" },
	{ 0x6E, "Green Video Black Level" },
	{ 0x70, "Blue Video Black Level" },

	// non-contiguous, read/write
	{ 0x5E, "Input Level Select" },
	{ 0x60, "Input Source Select" },
	{ 0xCA, "On Screen Display" },
	{ 0xD4, "StereoMode" },

	// non-contiguous, read-only
	{ 0xA2, "Auto Size Center" },
	{ 0xA4, "Polarity Horizontal Synchronization" },
	{ 0xA6, "Polarity Vertical Synchronization" },
	{ 0xA8, "Synchronization Type" },
	{ 0xAA, "Screen Orientation" },
	{ 0xAC, "Horizontal Frequency" },
	{ 0xAE, "Vertical Frequency" },

	// write-only
	{ 0x01, "Degauss" },
	{ 0xB0, "Settings" },

	{ 0, "(unknown)" }
};

static const char *controlName(int usage_code)
{
	int hi = (usage_code >> 16) & 0xFFFF;
	int lo = usage_code & 0xFFFF;
	struct controlData *c;

	switch (hi)
	{
	case 0x80: return "(USB Monitor usage page)";
	case 0x81: return "(USB Enumerated Values usage page)";
	case 0x82: c = controls82; break;
	case 0x83: return "(Reserved usage page)";
	default: return "(unknown usage page)";
	}
	
	while (c->value)
	{
		if (c->value == lo)
			break;
		c++;
	}

	return c->name;
}

static void showReports(int fd, unsigned report_type)
{
	struct hiddev_report_info rinfo;
	struct hiddev_field_info finfo;
	struct hiddev_usage_ref uref;
	int i, j, ret;

	rinfo.report_type = report_type;
	rinfo.report_id = HID_REPORT_ID_FIRST;
	ret = ioctl(fd, HIDIOCGREPORTINFO, &rinfo);
	while (ret >= 0)
	{
		printf("HIDIOCGREPORTINFO: report_id=0x%X (%u fields)\n",
			rinfo.report_id, rinfo.num_fields);
		for (i = 0; i < rinfo.num_fields; i++)
		{
			finfo.report_type = rinfo.report_type;
			finfo.report_id   = rinfo.report_id;
			finfo.field_index = i;
			ioctl(fd, HIDIOCGFIELDINFO, &finfo);

			printf("HIDIOCGFIELDINFO: field_index=%u maxusage=%u flags=0x%X\n"
				"\tphysical=0x%X logical=0x%X application=0x%X\n"
				"\tlogical_minimum=%d,maximum=%d physical_minimum=%d,maximum=%d\n",
				finfo.field_index, finfo.maxusage, finfo.flags,
				finfo.physical, finfo.logical, finfo.application,
				finfo.logical_minimum,  finfo.logical_maximum,
				finfo.physical_minimum, finfo.physical_maximum);

			for (j = 0; j < finfo.maxusage; j++)
			{
				uref.report_type = finfo.report_type;
				uref.report_id   = finfo.report_id;
				uref.field_index = i;
				uref.usage_index = j;
				ioctl(fd, HIDIOCGUCODE, &uref);
				ioctl(fd, HIDIOCGUSAGE, &uref);

				printf(" >> usage_index=%u usage_code=0x%X (%s) value=%d\n",
					uref.usage_index,
					uref.usage_code,
					controlName(uref.usage_code),
					uref.value);

			}
		}
		printf("\n");

		rinfo.report_id |= HID_REPORT_ID_NEXT;
		ret = ioctl(fd, HIDIOCGREPORTINFO, &rinfo);
	}
}

static struct option long_options[] =
{
	{ "brightness", 1, 0, 'b' },
	{ "contrast",   1, 0, 'c' },
	{ "degauss",    0, 0, 'd' },
	{ "info",       0, 0, 'i' },
	{ 0 }
};

static __s32 checkS32Arg(const char *arg)
{
	__s32 result;
	char *nextc;

	if (arg == NULL)
		return -1;

	result = strtol(arg, &nextc, 10);
	if (*nextc != 0)
	{
		fprintf(stderr, "argument %s not valid\n", arg);
		exit(EXIT_FAILURE);
	}

	return result;
}

/* TODO: parametrize high word of usage_code */
static void writeUsage(int fd, unsigned report_type, unsigned code, __s32 value)
{
	struct hiddev_report_info rinfo;
	struct hiddev_field_info finfo;
	struct hiddev_usage_ref uref;

printf("### writeUsage %d = %d\n", code, value);

	/* find the requested usage code */
	uref.report_type = report_type;
	uref.report_id   = HID_REPORT_ID_UNKNOWN;
	uref.usage_code  = (0x82 << 16) | code;
	if (ioctl(fd, HIDIOCGUSAGE, &uref) < 0)
	{
		perror("HIDIOCGUSAGE");
		return;
	}

printf(" >> usage_index=%u usage_code=0x%X (%s) value=%d\n",
 uref.usage_index,
 uref.usage_code,
 controlName(uref.usage_code),
 uref.value);

	/* retrieve field info */
	finfo.report_type = uref.report_type;
	finfo.report_id   = uref.report_id;
	finfo.field_index = uref.field_index;
	if (ioctl(fd, HIDIOCGFIELDINFO, &finfo) < 0)
	{
		perror("HIDIOCGFIELDINFO");
		return;
	}

printf("HIDIOCGFIELDINFO: field_index=%u maxusage=%u flags=0x%X\n"
 "\tphysical=0x%X logical=0x%X application=0x%X\n"
 "\tlogical_minimum=%d,maximum=%d physical_minimum=%d,maximum=%d\n",
 finfo.field_index, finfo.maxusage, finfo.flags,
 finfo.physical, finfo.logical, finfo.application,
 finfo.logical_minimum,  finfo.logical_maximum,
 finfo.physical_minimum, finfo.physical_maximum);

	/* check limits */
	/* TODO: are the logical limits the ones we want to check against? */
	if ((value < finfo.logical_minimum)
	 || (value > finfo.logical_maximum))
	{
		printf("%s: value %d outside of allowed range (%d-%d)\n",
			controlName(uref.usage_code), value,
			finfo.logical_minimum,  finfo.logical_maximum);
		return;
	}

	/* set value */
	uref.value = value;
	if (ioctl(fd, HIDIOCSUSAGE, &uref) < 0)
	{
		perror("HIDIOCSUSAGE");
		return;
	}
printf("HIDIOCSUSAGE\n");

	rinfo.report_type = uref.report_type;
	rinfo.report_id   = uref.report_id;
	if (ioctl(fd, HIDIOCSREPORT, &rinfo) < 0)
	{
		perror("HIDIOCSREPORT");
		return;
	}
printf("HIDIOCSREPORT\n");
}

int main(int argc, char *argv[])
{
	int fd;
	int version;
	char name[100];
	struct hiddev_devinfo dinfo;
	int c;
	int opt_index;
	int degauss = 0, brightness = 0, contrast = 0, info = 0;
	__s32 brightness_value, contrast_value;

	optind = 0;
	while ((c = getopt_long(argc, argv, "b:c:d", long_options, &opt_index)) != -1)
	{
		switch (c)
		{
		case 'b':
			brightness = 1;
			brightness_value = checkS32Arg(optarg);
			break;

		case 'c':
			contrast = 1;
			contrast_value = checkS32Arg(optarg);
			break;

		case 'd':
			degauss = 1;
			break;

		case 'i':
			info = 1;
			break;

		case '?':
			fprintf(stderr, "usage: %s [options] <device>\n", argv[0]);
			return EXIT_FAILURE;
		} /* switch (c) */
	} /* while */

	if (optind != argc - 1)
	{
		fprintf(stderr, "usage: %s [options] <device>\n", argv[0]);
		return EXIT_FAILURE;
	}

	//fd = open(argv[optind], O_RDONLY);
	fd = open(argv[optind], O_RDWR);
	if (fd >= 0)
	{
		if (ioctl(fd, HIDIOCGVERSION, &version) < 0)
			perror("HIDIOCGVERSION");
		else
		{
			printf("HIDIOCGVERSION: %d.%d\n", (version>>16) & 0xFFFF, version & 0xFFFF);
			if (version != HID_VERSION)
				printf("WARNING: version does not match compile-time version\n");
		}

		if (ioctl(fd, HIDIOCGDEVINFO, &dinfo) < 0)
			perror("HIDIOCGDEVINFO");
		else
		{
			printf("HIDIOCGDEVINFO: bustype=%d busnum=%d devnum=%d ifnum=%d\n"
				"\tvendor=0x%04hx product=0x%04hx version=0x%04hx\n"
				"\tnum_applications=%d\n",
				dinfo.bustype, dinfo.busnum, dinfo.devnum, dinfo.ifnum,
				dinfo.vendor, dinfo.product, dinfo.version, dinfo.num_applications);
		}

		if (ioctl(fd, HIDIOCINITREPORT) < 0)
			perror("HIDIOCINITREPORT");

		if (ioctl(fd, HIDIOCGNAME(99), name) < 0)
			perror("HIDIOCGNAME");
		else
		{
			name[99] = 0;
			printf("HIDIOCGNAME: %s\n", name);
		}

		if (info != 0)
		{
			printf("\n*** INPUT:\n"); showReports(fd, HID_REPORT_TYPE_INPUT);
			printf("\n*** OUTPUT:\n"); showReports(fd, HID_REPORT_TYPE_OUTPUT);
			printf("\n*** FEATURE:\n"); showReports(fd, HID_REPORT_TYPE_FEATURE);
		}

		if (brightness != 0)
			writeUsage(fd, HID_REPORT_TYPE_FEATURE, HIDMON_BRIGHTNESS, brightness_value);
		if (contrast != 0)
			writeUsage(fd, HID_REPORT_TYPE_FEATURE, HIDMON_CONTRAST, contrast_value);
		if (degauss != 0)
			writeUsage(fd, HID_REPORT_TYPE_OUTPUT, HIDMON_DEGAUSS, 1);

		close(fd);
		return 0;
	}
	else
	{
		perror(argv[optind]);
		return EXIT_FAILURE;
	}
}

/*** EOF ***/

