Files

384 lines
11 KiB
C
Raw Permalink Normal View History

2024-11-25 04:01:47 +01:00
/*
* 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"
#undef PROGRAM_NAME
#define PROGRAM_NAME "razer-bw-te-ctl"
// --- Utilities ---------------------------------------------------------------
2024-11-25 04:25:57 +01:00
/// Search for a device with given vendor and product ID.
/// This is quite similar to libusb_open_device_with_vid_pid().
2024-11-25 04:01:47 +01:00
static libusb_device_handle *
2024-11-25 04:25:57 +01:00
find_device(int vendor, int product, int *error)
2024-11-25 04:01:47 +01:00
{
2024-11-25 04:25:57 +01:00
libusb_device **list = NULL;
2024-11-25 04:01:47 +01:00
libusb_device_handle *handle = NULL;
2024-11-25 04:25:57 +01:00
int result = 0;
2024-11-25 04:01:47 +01:00
2024-11-25 04:25:57 +01:00
ssize_t len = libusb_get_device_list(NULL, &list);
if (len < 0) {
result = len;
2024-11-25 04:01:47 +01:00
goto out;
2024-11-25 04:25:57 +01:00
}
2024-11-25 04:01:47 +01:00
2024-11-25 04:25:57 +01:00
for (ssize_t i = 0; i < len; i++) {
2024-11-25 04:01:47 +01:00
libusb_device *device = list[i];
2024-11-25 04:25:57 +01:00
struct libusb_device_descriptor desc = {};
if ((result = libusb_get_device_descriptor(device, &desc)))
(void) libusb_strerror(result);
else if (desc.idVendor != vendor || desc.idProduct != product)
2024-11-25 04:01:47 +01:00
continue;
2024-11-25 04:25:57 +01:00
else if (!(result = libusb_open(device, &handle)))
2024-11-25 04:01:47 +01:00
break;
}
2024-11-25 04:25:57 +01:00
libusb_free_device_list(list, true);
2024-11-25 04:01:47 +01:00
out:
2024-11-25 04:25:57 +01:00
if (error != NULL && result != 0)
*error = result;
2024-11-25 04:01:47 +01:00
return handle;
}
// --- Device configuration ----------------------------------------------------
2024-11-25 04:25:57 +01:00
enum {
USB_VENDOR_RAZER = 0x1532,
USB_PRODUCT_RAZER_BW_TE = 0x011c,
2024-11-25 04:01:47 +01:00
2024-11-25 04:25:57 +01:00
USB_GET_REPORT = 0x01,
USB_SET_REPORT = 0x09,
2024-11-25 04:01:47 +01:00
2024-11-25 04:25:57 +01:00
BW_CTL_IFACE = 0,
};
2024-11-25 04:01:47 +01:00
2024-11-25 04:25:57 +01:00
/// Razer logo backlight mode.
enum bw_led_mode {
2024-11-25 04:01:47 +01:00
LED_BRIGHTNESS = 0,
LED_BLINK,
LED_PULSATE
};
2024-11-25 04:25:57 +01:00
/// Overall device configuration.
struct bw_config {
2024-11-25 04:01:47 +01:00
enum bw_led_mode led_mode;
unsigned led_brightness;
unsigned macro_led_on : 1;
unsigned macro_led_blinking : 1;
unsigned gaming_mode : 1;
};
2024-11-25 04:25:57 +01:00
/// Send a command to the mouse via SET_REPORT.
2024-11-25 04:01:47 +01:00
static int
2024-11-25 04:25:57 +01:00
bw_send_command(
libusb_device_handle *device, unsigned char *data, uint16_t length)
2024-11-25 04:01:47 +01:00
{
2024-11-25 04:25:57 +01:00
unsigned char packet[90] = {0x00};
assert(length <= sizeof packet - 5);
memcpy(packet + 5, data, length);
2024-11-25 04:01:47 +01:00
unsigned char checksum = 0;
while (length--)
checksum ^= data[length];
packet[sizeof packet - 2] = checksum;
2024-11-25 04:25:57 +01:00
// 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,
2024-11-25 04:01:47 +01:00
USB_SET_REPORT, 0x0300, 0x0000, packet, sizeof packet, 0);
return result < 0 ? result : 0;
}
2024-11-25 04:25:57 +01:00
/// Set Razer logo backlight mode.
2024-11-25 04:01:47 +01:00
static int
2024-11-25 04:25:57 +01:00
bw_set_led_mode(libusb_device_handle *device, enum bw_led_mode mode)
2024-11-25 04:01:47 +01:00
{
2024-11-25 04:25:57 +01:00
unsigned char cmd[] = {0x03, 0x03, 0x02, 0x01, 0x04, mode};
return bw_send_command(device, cmd, sizeof cmd);
2024-11-25 04:01:47 +01:00
}
2024-11-25 04:25:57 +01:00
/// Set Razer logo backlight brightness.
2024-11-25 04:01:47 +01:00
static int
2024-11-25 04:25:57 +01:00
bw_set_led_brightness(libusb_device_handle *device, unsigned char brightness)
2024-11-25 04:01:47 +01:00
{
2024-11-25 04:25:57 +01:00
unsigned char cmd[] = {0x03, 0x03, 0x03, 0x01, 0x04, brightness};
return bw_send_command(device, cmd, sizeof cmd);
2024-11-25 04:01:47 +01:00
}
2024-11-25 04:25:57 +01:00
/// Set the on/off state of the macro LED.
2024-11-25 04:01:47 +01:00
static int
2024-11-25 04:25:57 +01:00
bw_set_macro_led(libusb_device_handle *device, bool on)
2024-11-25 04:01:47 +01:00
{
2024-11-25 04:25:57 +01:00
unsigned char cmd[] = {0x03, 0x03, 0x00, 0x00, 0x07, on};
return bw_send_command(device, cmd, sizeof cmd);
2024-11-25 04:01:47 +01:00
}
2024-11-25 04:25:57 +01:00
/// Set whether the macro LED should blink.
2024-11-25 04:01:47 +01:00
static int
2024-11-25 04:25:57 +01:00
bw_set_macro_led_blinking(libusb_device_handle *device, bool blinking)
2024-11-25 04:01:47 +01:00
{
2024-11-25 04:25:57 +01:00
unsigned char cmd[] = {0x03, 0x03, 0x02, 0x00, 0x07, blinking};
return bw_send_command(device, cmd, sizeof cmd);
2024-11-25 04:01:47 +01:00
}
2024-11-25 04:25:57 +01:00
/// Set the gaming mode (whether the Windows key should be ignored).
2024-11-25 04:01:47 +01:00
static int
2024-11-25 04:25:57 +01:00
bw_set_gaming_mode(libusb_device_handle *device, bool on)
2024-11-25 04:01:47 +01:00
{
2024-11-25 04:25:57 +01:00
unsigned char cmd[] = {0x03, 0x03, 0x00, 0x01, 0x08, on};
return bw_send_command(device, cmd, sizeof cmd);
2024-11-25 04:01:47 +01:00
}
// --- Control utility ---------------------------------------------------------
2024-11-25 04:25:57 +01:00
struct options {
2024-11-25 04:01:47 +01:00
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
2024-11-25 04:25:57 +01:00
show_usage(const char *program_name)
2024-11-25 04:01:47 +01:00
{
2024-11-25 04:25:57 +01:00
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");
2024-11-25 04:01:47 +01:00
}
static void
2024-11-25 04:25:57 +01:00
parse_options(int argc, char *argv[],
2024-11-25 04:01:47 +01:00
struct options *options, struct bw_config *new_config)
{
2024-11-25 04:25:57 +01:00
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}
2024-11-25 04:01:47 +01:00
};
2024-11-25 04:25:57 +01:00
if (argc == 1) {
2024-12-26 12:26:19 +01:00
show_usage(argv[0]);
exit(EXIT_FAILURE);
2024-11-25 04:01:47 +01:00
}
int c;
2024-11-25 04:25:57 +01:00
while ((c = getopt_long(argc, argv, "h", long_opts, NULL)) != -1)
switch (c) {
2024-11-25 04:01:47 +01:00
case 'h':
2024-11-25 04:25:57 +01:00
show_usage(argv[0]);
exit(EXIT_SUCCESS);
2024-11-25 04:01:47 +01:00
case 'V':
2024-11-25 04:25:57 +01:00
printf(PROGRAM_NAME " " PROGRAM_VERSION "\n");
exit(EXIT_SUCCESS);
2024-11-25 04:01:47 +01:00
case 'l':
2024-11-25 04:25:57 +01:00
if (!strcasecmp(optarg, "normal")) {
2024-11-25 04:01:47 +01:00
new_config->led_mode = LED_BRIGHTNESS;
2024-11-25 04:25:57 +01:00
} else if (!strcasecmp(optarg, "blink")) {
2024-11-25 04:01:47 +01:00
new_config->led_mode = LED_BLINK;
2024-11-25 04:25:57 +01:00
} else if (!strcasecmp(optarg, "pulsate")) {
2024-11-25 04:01:47 +01:00
new_config->led_mode = LED_PULSATE;
2024-11-25 04:25:57 +01:00
} else {
fprintf(stderr, "Error: invalid LED mode: %s\n", optarg);
exit(EXIT_FAILURE);
2024-11-25 04:01:47 +01:00
}
options->set_led_mode = true;
break;
2024-11-25 04:25:57 +01:00
case 'L': {
2024-11-25 04:01:47 +01:00
char *end;
2024-11-25 04:25:57 +01:00
long bri = strtol(optarg, &end, 10);
if (!*optarg || *end || bri < 0 || bri > 255) {
fprintf(stderr, "Error: invalid LED brightness value\n");
exit(EXIT_FAILURE);
2024-11-25 04:01:47 +01:00
}
options->set_led_brightness = true;
break;
}
case 'm':
2024-11-25 04:25:57 +01:00
if (!strcasecmp(optarg, "off")) {
2024-11-25 04:01:47 +01:00
new_config->macro_led_on = false;
new_config->macro_led_blinking = false;
2024-11-25 04:25:57 +01:00
} else if (!strcasecmp(optarg, "blink")) {
2024-11-25 04:01:47 +01:00
new_config->macro_led_on = true;
new_config->macro_led_blinking = true;
2024-11-25 04:25:57 +01:00
} else if (!strcasecmp(optarg, "on")) {
2024-11-25 04:01:47 +01:00
new_config->macro_led_on = true;
new_config->macro_led_blinking = false;
2024-11-25 04:25:57 +01:00
} else {
fprintf(stderr, "Error: invalid macro LED mode: %s\n", optarg);
exit(EXIT_FAILURE);
2024-11-25 04:01:47 +01:00
}
options->set_macro_led_blinking = true;
options->set_macro_led_on = true;
break;
case 'g':
2024-11-25 04:25:57 +01:00
if (!strcasecmp(optarg, "true") ||
!strcasecmp(optarg, "on") ||
!strcasecmp(optarg, "yes")) {
2024-11-25 04:01:47 +01:00
new_config->gaming_mode = true;
2024-12-26 12:26:19 +01:00
} else if (!strcasecmp(optarg, "false") ||
2024-11-25 04:25:57 +01:00
!strcasecmp(optarg, "off") ||
!strcasecmp(optarg, "no")) {
2024-11-25 04:01:47 +01:00
new_config->gaming_mode = false;
2024-11-25 04:25:57 +01:00
} else {
fprintf(stderr, "Error: invalid gaming mode"
2024-11-25 04:01:47 +01:00
" setting: %s\n", optarg);
2024-11-25 04:25:57 +01:00
exit(EXIT_FAILURE);
2024-11-25 04:01:47 +01:00
}
options->set_gaming_mode = true;
break;
case '?':
2024-11-25 04:25:57 +01:00
exit(EXIT_FAILURE);
2024-11-25 04:01:47 +01:00
}
2024-11-25 04:25:57 +01:00
if (optind < argc) {
fprintf(stderr, "Error: extra parameters\n");
exit(EXIT_FAILURE);
2024-11-25 04:01:47 +01:00
}
}
static int
2024-11-25 04:25:57 +01:00
apply_options(libusb_device_handle *device,
2024-11-25 04:01:47 +01:00
struct options *options, struct bw_config *new_config)
{
2024-11-25 04:25:57 +01:00
int result = 0;
2024-11-25 04:01:47 +01:00
if (options->set_led_mode)
2024-11-25 04:25:57 +01:00
if ((result = bw_set_led_mode(
device, new_config->led_mode)))
2024-11-25 04:01:47 +01:00
return result;
if (options->set_led_brightness)
2024-11-25 04:25:57 +01:00
if ((result = bw_set_led_brightness(
device, new_config->led_brightness)))
2024-11-25 04:01:47 +01:00
return result;
if (options->set_macro_led_on)
2024-11-25 04:25:57 +01:00
if ((result = bw_set_macro_led(
device, new_config->macro_led_on)))
2024-11-25 04:01:47 +01:00
return result;
if (options->set_macro_led_blinking)
2024-11-25 04:25:57 +01:00
if ((result = bw_set_macro_led_blinking(
device, new_config->macro_led_blinking)))
2024-11-25 04:01:47 +01:00
return result;
if (options->set_gaming_mode)
2024-11-25 04:25:57 +01:00
if ((result = bw_set_gaming_mode(
device, new_config->gaming_mode)))
2024-11-25 04:01:47 +01:00
return result;
return 0;
}
2024-11-25 05:24:02 +01:00
#define FAIL(label, ...) \
2024-11-25 04:25:57 +01:00
do { \
fprintf(stderr, "Error: " __VA_ARGS__); \
status = 1; \
goto label; \
2024-11-25 04:01:47 +01:00
} while (0)
int
2024-11-25 04:25:57 +01:00
main(int argc, char *argv[])
2024-11-25 04:01:47 +01:00
{
2024-11-25 04:25:57 +01:00
struct options options = {0};
struct bw_config new_config = {0};
2024-11-25 04:01:47 +01:00
2024-11-25 04:25:57 +01:00
parse_options(argc, argv, &options, &new_config);
2024-11-25 04:01:47 +01:00
int result, status = 0;
2024-11-25 04:25:57 +01:00
if ((result = libusb_init(NULL)))
2024-11-25 05:24:02 +01:00
FAIL(error_0, "libusb initialisation failed: %s\n",
2024-11-25 04:25:57 +01:00
libusb_error_name(result));
2024-11-25 04:01:47 +01:00
result = 0;
2024-11-25 04:25:57 +01:00
libusb_device_handle *device =
find_device(USB_VENDOR_RAZER, USB_PRODUCT_RAZER_BW_TE, &result);
if (!device) {
2024-11-25 04:01:47 +01:00
if (result)
2024-11-25 05:24:02 +01:00
FAIL(error_1, "couldn't open device: %s\n",
2024-11-25 04:25:57 +01:00
libusb_error_name(result));
2024-11-25 04:01:47 +01:00
else
2024-11-25 05:24:02 +01:00
FAIL(error_1, "no suitable device found\n");
2024-11-25 04:01:47 +01:00
}
bool reattach_driver = false;
2024-11-25 04:25:57 +01:00
switch ((result = libusb_kernel_driver_active(device, BW_CTL_IFACE))) {
2024-11-25 04:01:47 +01:00
case 0:
case LIBUSB_ERROR_NOT_SUPPORTED:
break;
case 1:
reattach_driver = true;
2024-11-25 04:25:57 +01:00
if ((result = libusb_detach_kernel_driver(device, BW_CTL_IFACE)))
2024-11-25 05:24:02 +01:00
FAIL(error_2, "couldn't detach kernel driver: %s\n",
2024-11-25 04:25:57 +01:00
libusb_error_name(result));
2024-11-25 04:01:47 +01:00
break;
default:
2024-11-25 05:24:02 +01:00
FAIL(error_2, "coudn't detect kernel driver presence: %s\n",
2024-11-25 04:25:57 +01:00
libusb_error_name(result));
2024-11-25 04:01:47 +01:00
}
2024-11-25 04:25:57 +01:00
if ((result = libusb_claim_interface(device, BW_CTL_IFACE)))
2024-11-25 05:24:02 +01:00
FAIL(error_3, "couldn't claim interface: %s\n",
2024-11-25 04:25:57 +01:00
libusb_error_name(result));
2024-11-25 04:01:47 +01:00
2024-11-25 04:25:57 +01:00
if ((result = apply_options(device, &options, &new_config)))
2024-11-25 05:24:02 +01:00
FAIL(error_4, "operation failed: %s\n",
2024-12-26 12:26:19 +01:00
libusb_error_name(result));
2024-11-25 04:01:47 +01:00
error_4:
2024-11-25 04:25:57 +01:00
if ((result = libusb_release_interface(device, BW_CTL_IFACE)))
2024-11-25 05:24:02 +01:00
FAIL(error_3, "couldn't release interface: %s\n",
2024-11-25 04:25:57 +01:00
libusb_error_name(result));
2024-11-25 04:01:47 +01:00
error_3:
2024-11-25 04:25:57 +01:00
if (reattach_driver) {
if ((result = libusb_attach_kernel_driver(device, BW_CTL_IFACE)))
2024-11-25 05:24:02 +01:00
FAIL(error_2, "couldn't reattach kernel driver: %s\n",
2024-11-25 04:25:57 +01:00
libusb_error_name(result));
2024-11-25 04:01:47 +01:00
}
error_2:
2024-11-25 04:25:57 +01:00
libusb_close(device);
2024-11-25 04:01:47 +01:00
error_1:
2024-11-25 04:25:57 +01:00
libusb_exit(NULL);
2024-11-25 04:01:47 +01:00
error_0:
return status;
}