aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt57
-rw-r--r--LICENSE15
-rw-r--r--README28
-rw-r--r--config.h.in7
-rw-r--r--razer-bw-te-ctl.c427
5 files changed, 534 insertions, 0 deletions
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 <p.janouch@gmail.com>")
+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 <p.janouch@gmail.com>
+ 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 <p.janouch@gmail.com>
+ * 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 <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;
+}
+