/*
 * gcc -o vx_hack vx_hack.c
 *
 * MX/VX/VX Nano proof of concept code
 *
 * (c) Andreas Schneider <anschneider@suse.de>
 *
 * License: GPLv2 or later
 */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <asm/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/hiddev.h>

#define VERSION "0.0.1"

#define VENDOR 0x046d
#define MOUSE_VX 0xc518
#define MOUSE_VX_NANO 0xc521
#define MOUSE_MX 0xc51a
#define MOUSE_MX_AIR 0xc525

static int send_report(int fd, int id, const unsigned int *buf, size_t size) {
  struct hiddev_report_info rinfo;
  struct hiddev_usage_ref uref;
  int i, err;

  for (i = 0; i < size; i++) {
    memset(&uref, 0, sizeof(uref));
    uref.report_type = HID_REPORT_TYPE_OUTPUT;
    uref.report_id   = id;
    uref.field_index = 0;
    uref.usage_index = i;
    uref.usage_code  = 0xff000001;
    uref.value       = buf[i];

    err = ioctl(fd, HIDIOCSUSAGE, &uref);
    if (err < 0)
      return err;
  }

  memset(&rinfo, 0, sizeof(rinfo));
  rinfo.report_type = HID_REPORT_TYPE_OUTPUT;
  rinfo.report_id   = id;
  rinfo.num_fields  = 1;
  err = ioctl(fd, HIDIOCSREPORT, &rinfo);

  return err;
}

static int query_report(int fd, int id, unsigned int *buf, size_t size) {
  struct hiddev_usage_ref_multi uref;
  struct hiddev_report_info rinfo;
  int i = 0, rc = -1;

  rinfo.report_type = HID_REPORT_TYPE_INPUT;
  rinfo.report_id = id;
  rinfo.num_fields = 1;
  rc = ioctl(fd, HIDIOCGREPORT, &rinfo);
  if (rc < 0) {
    perror("HIDIOCGREPORT");
    return rc;
  }

  uref.uref.report_type = HID_REPORT_TYPE_INPUT;
  uref.uref.report_id = id;
  uref.uref.field_index = 0;
  uref.uref.usage_index = 0;
  uref.num_values = size;
  rc = ioctl(fd, HIDIOCGUSAGES, &uref);
  if (rc < 0) {
    perror("HIDIOCGUSAGES");
    return rc;
  }

  for (i = 0; i < size; i++) {
    buf[i] = uref.values[i];
  }

  return rc;
}

void send_msg(int fd, int id, int c0, int c1, int c2, int c3, int c4, int c5)
{
  int rc = -1;
  unsigned int b[6];

  b[0] = c0;
  b[1] = c1;
  b[2] = c2;
  b[3] = c3;
  b[4] = c4;
  b[5] = c5;

  rc = send_report(fd, id, b, 6);

  if (rc < 0) {
    perror("error sending to device");
    close(fd);
    exit(1);
  }
}

int main (int argc, char **argv) {

  int fd = -1;
  int i = 0;
  int version = 0;
  struct hiddev_devinfo device_info;
  unsigned int buf[6] = {0};

  printf("VX hack version: %s\n\n", VERSION);

  /* ioctl() requires a file descriptor, so we check we got one, and
     then open it */
  if (argc != 2) {
    fprintf(stderr, "Usage: %s hiddevice\n - hiddevice probably /dev/usb/hiddev0\n", argv[0]);
    exit(1);
  }
  if ((fd = open(argv[1], O_RDONLY)) < 0) {
    perror("hiddev open");
    exit(1);
  }

  /* ioctl() accesses the underlying driver */
  ioctl(fd, HIDIOCGVERSION, &version);

  /* the HIDIOCGVERSION ioctl() returns an int
   * so we unpack it and display it
   * we create a patch
   */
  printf("hiddev driver version is %d.%d.%d\n", version >> 16, (version >> 8) & 0xff, version & 0xff);

  /* suck out some device information */
  ioctl(fd, HIDIOCGDEVINFO, &device_info);

  /* the HIDIOCGDEVINFO ioctl() returns hiddev_devinfo
   * structure - see <linux/hiddev.h> 
   * So we work through the various elements, displaying 
   * each of them 
   */
  printf("vendor 0x%04hx product 0x%04hx version 0x%04hx ",
      device_info.vendor, device_info.product,
      device_info.version);
  printf("has %i application%s ", device_info.num_applications,
      (device_info.num_applications==1?"":"s"));
  printf("and is on bus: %d   devnum: %d   ifnum: %d\n",
      device_info.busnum, device_info.devnum,
      device_info.ifnum);

  /* We have a G5? */
  if((device_info.vendor == (short)VENDOR) &&
      ((device_info.product == (short)MOUSE_VX) ||
      (device_info.product == (short)MOUSE_VX_NANO) ||
      (device_info.product == (short)MOUSE_MX_AIR) ||
      (device_info.product == (short)MOUSE_MX))) {
    char dev[256] = {0};

    ioctl(fd, HIDIOCGNAME(255), &dev);

    if(device_info.product == (short)MOUSE_VX)
      printf(">>  VX Revolution (%s) detected!\n", dev);

    if(device_info.product == (short)MOUSE_VX_NANO)
      printf(">>  VX Nano (%s) detected!\n", dev);

    if(device_info.product == (short)MOUSE_MX)
      printf(">>  MX Revolution (%s) detected!\n", dev);

    if(device_info.product == (short)MOUSE_MX_AIR)
      printf(">>  MX Air (%s) detected!\n", dev);

    /*
     * Initialise the internal report structures
     */
    if (ioctl(fd, HIDIOCINITREPORT, 0) < 0) {
      perror("hid report init failed");
      exit(1);
    }

    printf(">> Battery information:\n");
    send_msg(fd, 0x10, 0x01, 0x81, 0x0d, 0x01, 0x00, 0x00);

    sleep(1);

    if (query_report(fd, 0x10, buf, 6) < 0) {
      perror("query failed");
      close(fd);
      exit(1);
    }

    printf("query result: ");
    for (i = 0; i < 6; i++) {
      printf(" %02x", buf[i]);
    }
    printf("\n");

    printf("battery status: %d%\n", buf[3] == 0x7f ? 0 : buf[3]);

    switch (buf[5]) {
      case 0x30:
        printf("battery: running on battery\n");
        break;
      case 0x50:
        printf("battery: charging\n");
        break;
      case 0x90:
        printf("battery: fully charged\n");
        break;
    }
    printf("could be electric current: %d\n", buf[4]);
  }

  close(fd);

  exit(0);
}

