Import razer-bw-te-ctl

This is for a very old keyboard that I no longer own.
This commit is contained in:
Přemysl Eric Janouch 2024-11-25 04:01:47 +01:00
parent f68bf51234
commit e46eee0a7f
Signed by: p
GPG Key ID: A0420B94F92B9493
4 changed files with 472 additions and 3 deletions

View File

@ -40,8 +40,32 @@ if (WITH_LIBUSB)
target_include_directories (elksmart-comm PUBLIC ${libusb_INCLUDE_DIRS})
target_link_directories (elksmart-comm PUBLIC ${libusb_LIBRARY_DIRS})
target_link_libraries (elksmart-comm ${libusb_LIBRARIES})
list (APPEND targets razer-bw-te-ctl)
add_executable (razer-bw-te-ctl razer-bw-te-ctl.c)
target_include_directories (razer-bw-te-ctl PUBLIC ${libusb_INCLUDE_DIRS})
target_link_directories (razer-bw-te-ctl PUBLIC ${libusb_LIBRARY_DIRS})
target_link_libraries (razer-bw-te-ctl ${libusb_LIBRARIES})
endif ()
# Generate documentation from help output
find_program (HELP2MAN_EXECUTABLE help2man)
if (NOT HELP2MAN_EXECUTABLE)
message (FATAL_ERROR "help2man not found")
endif ()
foreach (target ${targets})
set (page_output "${PROJECT_BINARY_DIR}/${target}.1")
list (APPEND project_MAN_PAGES "${page_output}")
add_custom_command (OUTPUT ${page_output}
COMMAND ${HELP2MAN_EXECUTABLE} -N
"${PROJECT_BINARY_DIR}/${target}" -o ${page_output}
DEPENDS ${target}
COMMENT "Generating man page for ${target}" VERBATIM)
endforeach ()
add_custom_target (docs ALL DEPENDS ${project_MAN_PAGES})
# The files to be installed
include (GNUInstallDirs)
@ -55,6 +79,12 @@ install (TARGETS ${targets} DESTINATION ${CMAKE_INSTALL_BINDIR}
SETUID)
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
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 ()
# CPack
set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch")
set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>")

View File

@ -1,4 +1,4 @@
Copyright (c) 2024, Přemysl Eric Janouch <p@janouch.name>
Copyright (c) 2013, 2024, 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.

View File

@ -15,14 +15,26 @@ and may not run at the same time, as it would contend for device access.
partially reimplementing the Ocrustar mobile app.
While it does not build for Windows, this is incidental.
- _razer-bw-te-ctl_ makes it possible to change the configuration of your Razer
BlackWidow Tournament Edition keybooard.
+
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.
Packages
--------
Regular releases are sporadic. git master should be stable enough.
You can get a package with the latest development version
as a https://git.janouch.name/p/nixexprs[Nix derivation].
Building
--------
Build dependencies: CMake, pkg-config, liberty (included) +
Runtime dependencies: libusb-1.0 (elksmart-comm), hidapi (eizoctl)
Build dependencies:
CMake, pkg-config, liberty (included), help2man +
Runtime dependencies:
libusb-1.0 (elksmart-comm, razer-bw-te-ctl), hidapi (eizoctl)
$ git clone --recursive https://git.janouch.name/p/usb-drivers.git
$ mkdir desktop-tools/build

427
razer-bw-te-ctl.c Normal file
View File

@ -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 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 ---------------------------------------------------------------
/** 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 (PROGRAM_NAME " " PROGRAM_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;
}