commit
97616b9600
5 changed files with 534 additions and 0 deletions
@ -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) |
@ -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. |
||||
|
@ -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 |
@ -0,0 +1,7 @@
|
||||
#ifndef CONFIG_H |
||||
#define CONFIG_H |
||||
|
||||
#define PROJECT_NAME "${CMAKE_PROJECT_NAME}" |
||||
#define PROJECT_VERSION "${project_VERSION}" |
||||
|
||||
#endif // ! CONFIG_H
|
@ -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; |
||||
} |
||||
|
Loading…
Reference in new issue