razer-bw-te-ctl/razer-bw-te-ctl.c

426 lines
11 KiB
C

/*
* razer-bw-te-ctl.c: Razer BlackWidow Tournament Edition control utility
*
* Everything has been reverse-engineered via Wireshark/usbmon and VirtualBox.
*
* Copyright (c) 2013, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include <getopt.h>
#include <strings.h>
#include <libusb.h>
#include "config.h"
// --- Utilities ---------------------------------------------------------------
/** Search for a device with given vendor and product ID. */
static libusb_device_handle *
find_device (int vendor, int product, int *error)
{
libusb_device **list;
libusb_device *found = NULL;
libusb_device_handle *handle = NULL;
int err = 0;
ssize_t cnt = libusb_get_device_list (NULL, &list);
if (cnt < 0)
goto out;
ssize_t i = 0;
for (i = 0; i < cnt; i++)
{
libusb_device *device = list[i];
struct libusb_device_descriptor desc;
if (libusb_get_device_descriptor (device, &desc))
continue;
if (desc.idVendor == vendor && desc.idProduct == product)
{
found = device;
break;
}
}
if (found)
{
err = libusb_open (found, &handle);
if (err)
goto out_free;
}
out_free:
libusb_free_device_list(list, 1);
out:
if (error != NULL && err != 0)
*error = err;
return handle;
}
// --- Device configuration ----------------------------------------------------
#define USB_VENDOR_RAZER 0x1532
#define USB_PRODUCT_RAZER_BW_TE 0x011c
#define USB_GET_REPORT 0x01
#define USB_SET_REPORT 0x09
#define BW_CTL_IFACE 0
/** Razer logo backlight mode. */
enum bw_led_mode
{
LED_BRIGHTNESS = 0,
LED_BLINK,
LED_PULSATE
};
/** Overall device configuration. */
struct bw_config
{
enum bw_led_mode led_mode;
unsigned led_brightness;
unsigned macro_led_on : 1;
unsigned macro_led_blinking : 1;
unsigned gaming_mode : 1;
};
/** Send a command to the mouse via SET_REPORT. */
static int
bw_send_command (libusb_device_handle *device,
unsigned char *data, uint16_t length)
{
unsigned char packet[90] = { 0x00 };
assert (length <= sizeof packet - 5);
memcpy (packet + 5, data, length);
unsigned char checksum = 0;
while (length--)
checksum ^= data[length];
packet[sizeof packet - 2] = checksum;
// XXX wIndex should actually be 0x0002 but that doesn't work
int result = libusb_control_transfer (device, LIBUSB_ENDPOINT_OUT
| LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE,
USB_SET_REPORT, 0x0300, 0x0000, packet, sizeof packet, 0);
return result < 0 ? result : 0;
}
/** Set Razer logo backlight mode. */
static int
bw_set_led_mode (libusb_device_handle *device, enum bw_led_mode mode)
{
unsigned char cmd[] = { 0x03, 0x03, 0x02, 0x01, 0x04, mode };
return bw_send_command (device, cmd, sizeof cmd);
}
/** Set Razer logo backlight brightness. */
static int
bw_set_led_brightness (libusb_device_handle *device, unsigned char brightness)
{
unsigned char cmd[] = { 0x03, 0x03, 0x03, 0x01, 0x04, brightness };
return bw_send_command (device, cmd, sizeof cmd);
}
/** Set the on/off state of the macro LED. */
static int
bw_set_macro_led (libusb_device_handle *device, bool on)
{
unsigned char cmd[] = { 0x03, 0x03, 0x00, 0x00, 0x07, on };
return bw_send_command (device, cmd, sizeof cmd);
}
/** Set whether the macro LED should blink. */
static int
bw_set_macro_led_blinking (libusb_device_handle *device, bool blinking)
{
unsigned char cmd[] = { 0x03, 0x03, 0x02, 0x00, 0x07, blinking };
return bw_send_command (device, cmd, sizeof cmd);
}
/** Set the gaming mode (whether the Windows key should be ignored). */
static int
bw_set_gaming_mode (libusb_device_handle *device, bool on)
{
unsigned char cmd[] = { 0x03, 0x03, 0x00, 0x01, 0x08, on };
return bw_send_command (device, cmd, sizeof cmd);
}
// --- Control utility ---------------------------------------------------------
struct options
{
unsigned set_led_mode : 1;
unsigned set_led_brightness : 1;
unsigned set_macro_led_on : 1;
unsigned set_macro_led_blinking : 1;
unsigned set_gaming_mode : 1;
};
static void
show_usage (const char *program_name)
{
printf ("Usage: %s [OPTION]...\n", program_name);
printf ("Configure Razer BlackWidow Tournament Edition devices.\n\n");
printf (" -h, --help Show this help\n");
printf (" --version Show program version and exit\n");
printf (" --led-mode X Set the mode of the Razer logo LED"
" (can be 'normal', 'blink' or 'pulsate')\n");
printf (" --led-brightness X Set Razer logo LED brightness"
" (from 0 to 255)\n");
printf (" --macro-led X Set the macro LED mode"
" ('off', 'on' or 'blink')\n");
printf (" --gaming-mode BOOL Set whether the Windows key is ignored\n");
printf ("\n");
}
static void
parse_options (int argc, char *argv[],
struct options *options, struct bw_config *new_config)
{
static struct option long_opts[] =
{
{ "help", no_argument, 0, 'h' },
{ "version", no_argument, 0, 'V' },
{ "led-mode", required_argument, 0, 'l' },
{ "led-brightness", required_argument, 0, 'L' },
{ "macro-led", required_argument, 0, 'm' },
{ "gaming-mode", required_argument, 0, 'g' },
{ 0, 0, 0, 0 }
};
if (argc == 1)
{
show_usage (argv[0]);
exit (EXIT_FAILURE);
}
int c;
while ((c = getopt_long (argc, argv, "h", long_opts, NULL)) != -1)
{
switch (c)
{
case 'h':
show_usage (argv[0]);
exit (EXIT_SUCCESS);
case 'V':
printf (PROJECT_NAME " " PROJECT_VERSION "\n");
exit (EXIT_SUCCESS);
case 'l':
if (!strcasecmp (optarg, "normal"))
new_config->led_mode = LED_BRIGHTNESS;
else if (!strcasecmp (optarg, "blink"))
new_config->led_mode = LED_BLINK;
else if (!strcasecmp (optarg, "pulsate"))
new_config->led_mode = LED_PULSATE;
else
{
fprintf (stderr, "Error: invalid LED mode: %s\n", optarg);
exit (EXIT_FAILURE);
}
options->set_led_mode = true;
break;
case 'L':
{
char *end;
long bri = strtol (optarg, &end, 10);
if (!*optarg || *end || bri < 0 || bri > 255)
{
fprintf (stderr, "Error: invalid LED brightness value\n");
exit (EXIT_FAILURE);
}
options->set_led_brightness = true;
break;
}
case 'm':
if (!strcasecmp (optarg, "off"))
{
new_config->macro_led_on = false;
new_config->macro_led_blinking = false;
}
else if (!strcasecmp (optarg, "blink"))
{
new_config->macro_led_on = true;
new_config->macro_led_blinking = true;
}
else if (!strcasecmp (optarg, "on"))
{
new_config->macro_led_on = true;
new_config->macro_led_blinking = false;
}
else
{
fprintf (stderr, "Error: invalid macro LED mode: %s\n", optarg);
exit (EXIT_FAILURE);
}
options->set_macro_led_blinking = true;
options->set_macro_led_on = true;
break;
case 'g':
if (!strcasecmp (optarg, "true")
|| !strcasecmp (optarg, "on")
|| !strcasecmp (optarg, "yes"))
new_config->gaming_mode = true;
else if (!strcasecmp (optarg, "false")
|| !strcasecmp (optarg, "off")
|| !strcasecmp (optarg, "no"))
new_config->gaming_mode = false;
else
{
fprintf (stderr, "Error: invalid gaming mode"
" setting: %s\n", optarg);
exit (EXIT_FAILURE);
}
options->set_gaming_mode = true;
break;
case '?':
exit (EXIT_FAILURE);
}
}
if (optind < argc)
{
fprintf (stderr, "Error: extra parameters\n");
exit (EXIT_FAILURE);
}
}
static int
apply_options (libusb_device_handle *device,
struct options *options, struct bw_config *new_config)
{
int result;
if (options->set_led_mode)
if ((result = bw_set_led_mode
(device, new_config->led_mode)))
return result;
if (options->set_led_brightness)
if ((result = bw_set_led_brightness
(device, new_config->led_brightness)))
return result;
if (options->set_macro_led_on)
if ((result = bw_set_macro_led
(device, new_config->macro_led_on)))
return result;
if (options->set_macro_led_blinking)
if ((result = bw_set_macro_led_blinking
(device, new_config->macro_led_blinking)))
return result;
if (options->set_gaming_mode)
if ((result = bw_set_gaming_mode
(device, new_config->gaming_mode)))
return result;
return 0;
}
#define ERROR(label, ...) \
do { \
fprintf (stderr, "Error: " __VA_ARGS__); \
status = 1; \
goto label; \
} while (0)
int
main (int argc, char *argv[])
{
struct options options = { 0 };
struct bw_config new_config = { 0 };
parse_options (argc, argv, &options, &new_config);
int result, status = 0;
result = libusb_init (NULL);
if (result)
ERROR (error_0, "libusb initialisation failed: %s\n",
libusb_error_name (result));
result = 0;
libusb_device_handle *device = find_device
(USB_VENDOR_RAZER, USB_PRODUCT_RAZER_BW_TE, &result);
if (!device)
{
if (result)
ERROR (error_1, "couldn't open device: %s\n",
libusb_error_name (result));
else
ERROR (error_1, "no suitable device found\n");
}
bool reattach_driver = false;
result = libusb_kernel_driver_active (device, BW_CTL_IFACE);
switch (result)
{
case 0:
case LIBUSB_ERROR_NOT_SUPPORTED:
break;
case 1:
reattach_driver = true;
result = libusb_detach_kernel_driver (device, BW_CTL_IFACE);
if (result)
ERROR (error_2, "couldn't detach kernel driver: %s\n",
libusb_error_name (result));
break;
default:
ERROR (error_2, "coudn't detect kernel driver presence: %s\n",
libusb_error_name (result));
}
result = libusb_claim_interface (device, BW_CTL_IFACE);
if (result)
ERROR (error_3, "couldn't claim interface: %s\n",
libusb_error_name (result));
result = apply_options (device, &options, &new_config);
if (result)
ERROR (error_4, "operation failed: %s\n",
libusb_error_name (result));
error_4:
result = libusb_release_interface (device, BW_CTL_IFACE);
if (result)
ERROR (error_3, "couldn't release interface: %s\n",
libusb_error_name (result));
error_3:
if (reattach_driver)
{
result = libusb_attach_kernel_driver (device, BW_CTL_IFACE);
if (result)
ERROR (error_2, "couldn't reattach kernel driver: %s\n",
libusb_error_name (result));
}
error_2:
libusb_close (device);
error_1:
libusb_exit (NULL);
error_0:
return status;
}