/*
 * lsadb.c
 * Copyright 2001-2005 by Matthias Grimm
 *
 * 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.
 *
 * Please be so kind to send me a copy of your modifications to
 *     Matthias Grimm <matthiasgrimm@users.sourceforge.net>
 *
 * Thanks to Jimi Xenidis for his programm fnset.c
*/


#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <linux/adb.h>
#include <linux/pmu.h>
#include <time.h>
#include <getopt.h>

#include "lsadb.h"

#define PMU_GET_VERSION         0xea    /* get pmu firmware version # */
#define ADB_BUFSIZE		16      /* Size of the ADB buffer */

const char *prog;

int ReadADBReg(int fd, unsigned char ADBBuffer[], int dev, int reg)
{
    int n;

    ADBBuffer[0] = ADB_PACKET;
    ADBBuffer[1] = ADB_READREG(dev, reg);
    n = write(fd, ADBBuffer, 2);
    if (n != 2) {
        if (n == -1) 
            fprintf(stderr, "%s: write(): %s\n", prog, strerror(errno));
        else
            fprintf(stderr, "%s: write(): expected 2 Bytes, got %d\n", prog, n);
        return -1;
    }
    n = read(fd, ADBBuffer, ADB_BUFSIZE);
    if (n == -1)
        fprintf(stderr, "%s: read(): %s\n", prog, strerror(errno));
    ADBBuffer[0] = n; /* replace the ADB command with data length */
    return n;
}

int ReadPMUVer(int fd)
{
    int n;
    unsigned char ADBBuffer[32];

    ADBBuffer[0] = PMU_PACKET;
    ADBBuffer[1] = PMU_GET_VERSION;
    n = write(fd, ADBBuffer, 2);
    if (n != 2) {
        if (n == -1) 
            fprintf(stderr, "%s: write(): %s\n", prog, strerror(errno));
        else
            fprintf(stderr, "%s: write(): expected 2 Bytes, got %d\n", prog, n);
        return -1;
    }
    n = read(fd, ADBBuffer, sizeof(ADBBuffer));
    if (n != 2) {
        if (n == -1) 
            fprintf(stderr, "%s: read(): %s\n", prog, strerror(errno));
        else
            fprintf(stderr, "%s: read(): expected 2 Bytes, got %d\n", prog, n);
        return -1;
    }
    return ADBBuffer[1];
}

int usecsleep(int usecs)
{
    struct timeval tv;
    
    tv.tv_sec = 0;
    tv.tv_usec = usecs;
    return select(0, NULL, NULL, NULL, &tv);
}

struct ADBDev *identify_device(int devtype, int devhandler)
{
     int i;
     struct ADBDev *Ident = &ADBDevices[0];
     
     for (i=1; i < sizeof(ADBDevices) / sizeof(struct ADBDev); i++)
     {
       if(ADBDevices[i].devicetype == devtype) {
	 if(ADBDevices[i].handler_id == devhandler)
	   return &ADBDevices[i];
         else if(ADBDevices[i].handler_id == 0)
	   Ident = &ADBDevices[i];
       }
     }
     
     return Ident;
}

void PrintRAWData(unsigned char data[])
{
    int i, j = 8;
    
    fprintf(stdout, "[");
    for (i=1; i < data[0]; i++) {
      if (i != 1)
        fprintf(stdout, " ");
      fprintf(stdout, "%02x", data[i]);
      j--;
    }
    fprintf(stdout, "]");
    for (; j > 0; j--)
      fprintf (stdout, "   ");
}

void regx_raw(unsigned char data[])
{
    PrintRAWData(data);
}

void reg0_stdkbd(unsigned char data[])
{
    struct adbreg0_stdkbd *reg;
    reg = (struct adbreg0_stdkbd*) &data[1];
    
    PrintRAWData(data);
    fprintf(stdout, " scancode0 %02x %s\n                                 scancode1 %02x %s",
        reg->keycode0, reg->keyup0 ? "released" : "pressed",
	reg->keycode1, reg->keyup1 ? "released" : "pressed");
}

void reg1_extmouse(unsigned char data[])
{
    int i, dc;
    unsigned char mousetype[5];

    for(i=0 ; i<4 ; i++)
      mousetype[i] = data[i+1];
    mousetype[4] = 0;

    dc = data[7];
    if (dc > MAXDEVICECLASS)
      dc = 0;  /* unknown */
    else  
      dc += 1;
    
    PrintRAWData(data);
    fprintf(stdout, " Mouse type:   %s\n"\
                    "                                 resolution:   %d dpi\n"\
                    "                                 Device class: %s\n"\
                    "                                 buttons:      %d",
        mousetype, data[5]*256+data[6], deviceclasses[dc], data[8]);
}

void reg1_pbg3kbd(unsigned char data[])
{
    struct adbreg1_pbg3kbd *reg;
    reg = (struct adbreg1_pbg3kbd*) &data[1];

    PrintRAWData(data);
    fprintf(stdout, " %s need Fn Key",
	reg->primfkeys ? "Special keys" : "Function keys");
}

void reg2_stdkbd(unsigned char data[])
{
    struct adbreg2_stdkbd *reg;
    reg = (struct adbreg2_stdkbd*) &data[1];

    PrintRAWData(data);    
    fprintf(stdout, " Qualifier: (not all must be supported)\n"\
                    "                                 %sFn-key%s %sDelete%s %sCaps-Lock%s %sReset%s    %sControl%s\n"\
                    "                                 %sShift%s  %sOption%s %sApple%s     %sNum-Lock%s %sScroll-Lock%s\n"\
                    "                                 %sScroll-Lock-LED%s %sCaps-Lock-LED%s %sNum-Lock-LED%s",
        reg->fnkey ? " " : "[", reg->fnkey ? " " : "]",
        reg->deletekey ? " " : "[", reg->deletekey ? " " : "]",
        reg->capslockkey ? " " : "[", reg->capslockkey ? " " : "]",
        reg->resetkey ? " " : "[", reg->resetkey ? " " : "]",
        reg->controlkey ? " " : "[", reg->controlkey ? " " : "]",
        reg->shiftkey ? " " : "[", reg->shiftkey ? " " : "]",
        reg->optionkey ? " " : "[", reg->optionkey ? " " : "]",
        reg->applekey ? " " : "[", reg->applekey ? " " : "]",
        reg->numlockkey ? " " : "[", reg->numlockkey ? " " : "]",
        reg->scrolllockkey ? " " : "[", reg->scrolllockkey ? " " : "]",
        reg->scrolllockled ? " " : "[", reg->scrolllockled ? " " : "]",
        reg->capslockled ? " " : "[", reg->capslockled ? " " : "]",
	reg->numlockled ? " " : "[", reg->numlockled ? " " : "]");
}



void PrintDevID(struct ADBDev *dev, struct adbreg3 *reg, unsigned char data[])
{
    PrintRAWData(data);
    fprintf(stdout, " %s\n"\
                    "                                 %s %s\n"\
		    "                                 Exceptional Event %s\n"\
		    "                                 Service Request %s\n",
        devicetypes[dev->devicetype], dev->name, kbdlayouts[dev->layout],
	reg->exev ? "enabled" : "disabled", reg->sre ? "enabled" : "disabled");
};

int
evaluate_args(int argc, char *argv[])
{
	struct option const long_options[] = {
		  {"help", no_argument, 0, ARG_HELP},
		  {"version", no_argument, 0, ARG_VERSION},
		  {NULL, 0, NULL, 0}
	};
	char *prgname;
	int c;

	if((prgname = strrchr(argv[0],'/')) == NULL)
		prgname = argv[0];
	else prgname++;		/* ignore first slash*/
	argv[0] = prgname;

	while ((c = getopt_long (argc, argv, ARG_ALL, long_options, (int *) 0)) != EOF) {
		switch (c) {
			case ARG_VERSION:
				printf("%s, version %s, (c) 2002-2005 Matthias Grimm\n", PACKAGE, VERSION);
				return 1;
			case ARG_HELP:
			default:
				printf("%s - scans the ADB bus for devices.\n", prgname);
				printf("Usage: %s [OPTION]\n", prgname);
				printf ("Options:\n"
					"   -%c, --help               display this help text and exit\n"
					"   -%c, --version            display version information and exit\n",
					ARG_HELP, ARG_VERSION);
				return 1;
		}
	}
	return 0;
}

int
main(int argc, char *argv[])
{
    const char *adb = "/dev/adb";
    unsigned char data[ADB_BUFSIZE];
    struct ADBDev *DevID;
    struct adbreg3 *Reg03;

    int dev, reg, pmu;
    int fd;
    int n;

	if ((evaluate_args(argc, argv)) == 1)
		return 0;  /* help text printed, we are finish here */

    prog = argv[0];   /* name of programm, which was executed */

    fd = open(adb, O_RDWR);
    if (fd < 0) {
        fprintf(stderr, "%s: open(%s): %s\n", prog, adb, strerror(errno));
        return 1;
    }

    pmu = ReadPMUVer(fd);

    fprintf(stdout, "\nDevices connected to the ADB-Bus: (PMU: %d)\n\n", pmu);
    fprintf(stdout, "  D R  raw data                  decoded data\n");
    fprintf(stdout, "-------------------------------------------------------------------------------------\n");

    usecsleep(100000);            /* desync with keyboard, so that no keycode was robbed */
    for (dev = 1; dev < 16; dev++) {
	n = ReadADBReg(fd, data, dev, 3);
	if (n > 0) {
	    Reg03 = (struct adbreg3*) &data[1];   /* data[0] = ADB command, we could ignore it */
	    DevID = identify_device(dev & 7, Reg03->devhandler);

    	    fprintf(stdout, " %2d.%d: ", dev, 3);
	    PrintDevID(DevID, Reg03, data);
	    for (reg=2; reg >= 0 ; reg--)
	    {
        	fprintf(stdout, "    %d: ", reg);
		n = ReadADBReg(fd, data, dev, reg);
		if (n > 0) {
		  switch (reg)
		  {
		    case 2: if (DevID->REG2_decode)
			      (*DevID->REG2_decode)(data);
			    break;
		    case 1: if (DevID->REG1_decode)
			      (*DevID->REG1_decode)(data);
			    break;
		    case 0: if (DevID->REG0_decode)
			      (*DevID->REG0_decode)(data);
		  }
		}
		fprintf(stdout, "\n");    /* close register output with linefeed */
	    }
	    fprintf(stdout, "\n");
	}
    }

    close(fd);
    return 0;
}

