205 lines
6.5 KiB
Python
Executable File
205 lines
6.5 KiB
Python
Executable File
#!/usr/bin/python3
|
|
|
|
from optparse import OptionParser
|
|
import usb
|
|
import sys
|
|
|
|
USB_REQUEST_TYPE = 0x21 # Host To Device | Class | Interface
|
|
USB_REQUEST = 0x09 # SET_REPORT
|
|
|
|
USB_VALUE = 0x0300
|
|
USB_INDEX = 0x2
|
|
USB_INTERFACE = 2
|
|
|
|
VENDOR_ID = 0x1532 # Razer
|
|
PRODUCT_ID_BLACK_WIDOW = 0x010e # BlackWidow
|
|
PRODUCT_ID_BLACK_WIDOW_ULTIMATE = 0x011a # BlackWidow Ultimate
|
|
PRODUCT_ID_BLACK_WIDOW_2013 = 0x011b # BlackWidow 2013/2014
|
|
|
|
PRODUCTS = [("Black Widow", PRODUCT_ID_BLACK_WIDOW),
|
|
("Black Widow Ultimate", PRODUCT_ID_BLACK_WIDOW_ULTIMATE),
|
|
("Black Widow 2013/2014", PRODUCT_ID_BLACK_WIDOW_2013)]
|
|
|
|
LOG=sys.stderr.write
|
|
|
|
class BlackWidow(object):
|
|
def __init__(self):
|
|
self.kernel_driver_detached = False
|
|
self.interface_claimed = False
|
|
self.detected_keyboard = None
|
|
|
|
self.find_device()
|
|
if self.device_found():
|
|
self.claim_interface()
|
|
|
|
def __del__(self):
|
|
if self.interface_claimed:
|
|
self.release_interface()
|
|
|
|
def find_device(self):
|
|
for product_name, product_id in PRODUCTS:
|
|
self.device = usb.core.find(idVendor=VENDOR_ID, idProduct=product_id)
|
|
if self.device:
|
|
detected_keyboard = product_id
|
|
LOG("Found device: %s\n" % product_name)
|
|
break
|
|
|
|
def device_found(self):
|
|
return self.device is not None
|
|
|
|
def claim_interface(self):
|
|
try:
|
|
if self.device.is_kernel_driver_active(USB_INTERFACE):
|
|
LOG("Kernel driver active; detaching it\n")
|
|
|
|
self.device.detach_kernel_driver(USB_INTERFACE)
|
|
self.kernel_driver_detached = True
|
|
|
|
LOG("Claiming interface\n")
|
|
usb.util.claim_interface(self.device, USB_INTERFACE)
|
|
self.interface_claimed = True
|
|
except:
|
|
LOG("Unable to claim interface. Ensure the script is running as root.\n")
|
|
raise
|
|
|
|
def release_interface(self):
|
|
if self.interface_claimed:
|
|
LOG("Releasing claimed interface\n")
|
|
usb.util.release_interface(self.device, USB_INTERFACE)
|
|
|
|
if self.kernel_driver_detached:
|
|
LOG("Reattaching the kernel driver\n")
|
|
self.device.attach_kernel_driver(USB_INTERFACE)
|
|
|
|
def format_command(self, command):
|
|
from functools import reduce
|
|
c1 = bytes.fromhex(command)
|
|
c2 = [ reduce(int.__xor__, c1) ]
|
|
b = [0] * 90
|
|
b[5:5+len(c1)] = c1
|
|
b[-2:-1] = c2
|
|
return bytes(b)
|
|
|
|
def send(self, command):
|
|
def internal_send(msg):
|
|
USB_BUFFER = self.format_command(command)
|
|
result = 0
|
|
try:
|
|
result = self.device.ctrl_transfer(USB_REQUEST_TYPE, USB_REQUEST, wValue=USB_VALUE, wIndex=USB_INDEX, data_or_wLength=USB_BUFFER)
|
|
except:
|
|
sys.stderr.write("Could not send data\n")
|
|
if result == len(USB_BUFFER):
|
|
LOG("Data sent successfully\n")
|
|
return result
|
|
|
|
if isinstance(command, list):
|
|
for i in command:
|
|
print(' >> {}\n'.format(i))
|
|
internal_send(i)
|
|
elif isinstance(command, str):
|
|
internal_send(command)
|
|
|
|
def main():
|
|
#
|
|
init_new = '0200 0403'
|
|
init_old = '0200 0402'
|
|
no_pulsate = '0303 0201 0400'
|
|
pulsate = '0303 0201 0402'
|
|
brightness = '0303 0301 04'
|
|
backlight = '0303 0301 05'
|
|
gamemode = '0303 0001 08'
|
|
|
|
usage = "usage: %prog [options]"
|
|
parser = OptionParser(usage=usage)
|
|
parser.add_option("-i", "--init", action="store_true", dest="init", default=False, help="initiates the functionality of macro keys")
|
|
parser.add_option("-l", "--set-led", type="string", dest="led", default="unmodified", help="sets the status of the led (pulsate, bright, normal, dim, off or number between 0 - 255)", metavar="STATUS")
|
|
parser.add_option("-b", "--set-backlight", type="string", dest="backlight", default="unmodified", help="sets the status of the backlight (pulsate, bright, normal, dim, off or number between 0 - 255)")
|
|
parser.add_option("-g", "--set-game-mode", type="string", dest="gamemode", default="unmodified", help="sets whether the game mode is enabled (on/off)")
|
|
(options, args) = parser.parse_args()
|
|
|
|
bw = BlackWidow()
|
|
if bw.device_found():
|
|
# enable macro keys
|
|
if options.init == True:
|
|
LOG("Sending initiation command\n")
|
|
bw.send(init_old)
|
|
|
|
# set led status (doesn't work with BlackWidow 2016 yet)
|
|
if options.led == "unmodified":
|
|
pass
|
|
elif options.led == "pulsate":
|
|
LOG("Sending led pulsate command\n")
|
|
bw.send(pulsate)
|
|
elif options.led == "bright":
|
|
LOG("Sending no pulsate command and led bright command\n")
|
|
bw.send(no_pulsate)
|
|
bw.send(brightness + 'FF')
|
|
elif options.led == "normal":
|
|
LOG("Sending no pulsate command and led normal command\n")
|
|
bw.send(no_pulsate)
|
|
bw.send(brightness + 'a8')
|
|
elif options.led == "dim":
|
|
LOG("Sending no pulsate command and led dim command\n")
|
|
bw.send(no_pulsate)
|
|
bw.send(brightness + '54')
|
|
elif options.led == "off":
|
|
LOG("Sending no pulsate command and led off command\n")
|
|
bw.send(no_pulsate)
|
|
bw.send(brightness + '00')
|
|
else:
|
|
try:
|
|
brightness_level = int(options.led)
|
|
if brightness_level >= 0 and brightness_level <= 0xFF:
|
|
LOG("Sending no pulsate command and led command with custom brightness\n")
|
|
bw.send(no_pulsate)
|
|
bw.send(brightness + "{0:#0{1}x}".format(brightness_level,4)[2:])
|
|
else:
|
|
raise ValueError
|
|
except ValueError:
|
|
LOG("Specified value for led status \"%s\" is unknown and will be ignored.\n" % options.led)
|
|
|
|
# experimantal: set backlight (BlackWidow 2016 only)
|
|
if options.backlight == "unmodified":
|
|
pass
|
|
else:
|
|
if False: # condition for BlackWidow 2016
|
|
if options.backlight == "bright":
|
|
LOG("Sending blacklight command\n")
|
|
bw.send(backlight + 'FF')
|
|
elif options.backlight == "normal":
|
|
LOG("Sending blacklight command\n")
|
|
bw.send(backlight + 'a8')
|
|
elif options.backlight == "dim":
|
|
LOG("Sending blacklight command\n")
|
|
bw.send(backlight + '54')
|
|
elif options.backlight == "off":
|
|
LOG("Sending blacklight command\n")
|
|
bw.send(backlight + '00')
|
|
else:
|
|
try:
|
|
brightness_level = int(options.backlight)
|
|
if brightness_level >= 0 and brightness_level <= 0xFF:
|
|
bw.send(brightness + "{0:#0{1}x}".format(brightness_level,4)[2:])
|
|
else:
|
|
raise ValueError
|
|
except ValueError:
|
|
LOG("Specified value for backlight status \"%s\" is unknown and will be ignored.\n" % options.led)
|
|
else:
|
|
LOG("Setting the backlight is not supported by the detected keyboard.")
|
|
|
|
# enable/disable game mode
|
|
if options.gamemode == "unmodified":
|
|
pass
|
|
elif options.gamemode == "on":
|
|
bw.send(gamemode + '01')
|
|
elif options.gamemode == "off":
|
|
bw.send(gamemode + '00')
|
|
else:
|
|
LOG("Specified value for game mode is unknown and will be ignored.\n")
|
|
|
|
else:
|
|
LOG("No device found\n")
|
|
|
|
if __name__ == '__main__':
|
|
main()
|