From 97616b960093329cbca0df8c8657a9a9d3513ce9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Sun, 9 Jun 2013 17:19:00 +0200 Subject: [PATCH] Initial commit --- CMakeLists.txt | 57 +++++++ LICENSE | 15 ++ README | 28 +++ config.h.in | 7 + razer-bw-te-ctl.c | 427 ++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 534 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README create mode 100644 config.h.in create mode 100644 razer-bw-te-ctl.c diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..54baa01 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,57 @@ +cmake_minimum_required (VERSION 2.8.5) +project (razer-bw-te-ctl C) +set (project_VERSION "1.0") + +find_package (PkgConfig REQUIRED) +pkg_check_modules (dependencies REQUIRED libusb-1.0) +include_directories (${dependencies_INCLUDE_DIRS}) + +include (GNUInstallDirs) +configure_file (${PROJECT_SOURCE_DIR}/config.h.in + ${PROJECT_BINARY_DIR}/config.h) +include_directories (${PROJECT_BINARY_DIR}) + +add_executable (${PROJECT_NAME} ${PROJECT_NAME}.c) +target_link_libraries (${PROJECT_NAME} ${dependencies_LIBRARIES}) +install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR}) + +find_program (HELP2MAN_EXECUTABLE help2man) +if (NOT HELP2MAN_EXECUTABLE) + message (FATAL_ERROR "help2man not found") +endif () + +foreach (page ${PROJECT_NAME}) + set (page_output "${PROJECT_BINARY_DIR}/${page}.1") + list (APPEND project_MAN_PAGES "${page_output}") + add_custom_command (OUTPUT ${page_output} + COMMAND ${HELP2MAN_EXECUTABLE} -N + "${PROJECT_BINARY_DIR}/${page}" -o ${page_output} + DEPENDS ${PROJECT_NAME} + COMMENT "Generating man page for ${page}" VERBATIM) +endforeach () + +add_custom_target (docs ALL DEPENDS ${project_MAN_PAGES}) + +foreach (page ${project_MAN_PAGES}) + string (REGEX MATCH "\\.([0-9])" manpage_suffix "${page}") + install (FILES "${page}" + DESTINATION "${CMAKE_INSTALL_MANDIR}/man${CMAKE_MATCH_1}") +endforeach () + +set (CPACK_PACKAGE_DESCRIPTION_SUMMARY + "Razer BlackWidow Tournament Edition control utility") +set (CPACK_PACKAGE_VERSION ${project_VERSION}) +set (CPACK_PACKAGE_VENDOR "Premysl Janouch") +set (CPACK_PACKAGE_CONTACT "Přemysl Janouch ") +set (CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") + +set (CPACK_GENERATOR "TGZ;ZIP") +set (CPACK_PACKAGE_FILE_NAME + "${PROJECT_NAME}-${project_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") + +set (CPACK_SOURCE_GENERATOR "TGZ;ZIP") +set (CPACK_SOURCE_IGNORE_FILES "/\\\\.git;/build;/CMakeLists.txt.user") +set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${project_VERSION}") + +set (CPACK_SET_DESTDIR TRUE) +include (CPack) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6f18de6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ + Copyright (c) 2013, Přemysl Janouch + All rights reserved. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + 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. + diff --git a/README b/README new file mode 100644 index 0000000..ed3ce6a --- /dev/null +++ b/README @@ -0,0 +1,28 @@ +razer-bw-te-ctl + +This program makes it possible to change the configuration of your Razer +BlackWidow Tournament Edition keybooard from within Linux, *BSD or any other +POSIX-compatible system supported by libusb. + +Make sure to let the Windows Razer Synapse tool upgrade the firmware to the +newest version before running the program. There might be some issues otherwise +due to protocol changes, although I don't really deem it very probable. + +Run `razer-bw-te-ctl --help' or `man razer-bw-te-ctl' for usage information. + +Installation +============ +Build dependencies: cmake >= 2.8.5, help2man, libusb >= 1.0 + +$ git clone git://github.com/pjanouch/razer-bw-te-ctl.git +$ cd razer-bw-te-ctl +$ mkdir build +$ cd build +$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr +# make install + +Note that there's no "make uninstall". + +For Debian-based distros, you can do the following instead of the last step: +$ cpack -G DEB +# dpkg -i razer-bw-te-ctl-*.deb diff --git a/config.h.in b/config.h.in new file mode 100644 index 0000000..49193ff --- /dev/null +++ b/config.h.in @@ -0,0 +1,7 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#define PROJECT_NAME "${CMAKE_PROJECT_NAME}" +#define PROJECT_VERSION "${project_VERSION}" + +#endif // ! CONFIG_H diff --git a/razer-bw-te-ctl.c b/razer-bw-te-ctl.c new file mode 100644 index 0000000..6e16481 --- /dev/null +++ b/razer-bw-te-ctl.c @@ -0,0 +1,427 @@ +/* + * 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 Janouch + * All rights reserved. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * 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 +#include +#include +#include +#include + +#include +#include +#include + +#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; +} +