2015-07-31 22:16:11 +02:00
#!/usr/bin/python3
from optparse import OptionParser
import usb
import sys
2016-10-08 18:06:41 +02:00
USB_REQUEST_TYPE = 0x21 # Host To Device | Class | Interface
USB_REQUEST = 0x09 # SET_REPORT
2015-07-31 22:16:11 +02:00
USB_VALUE = 0x0300
USB_INDEX = 0x2
USB_INTERFACE = 2
2016-10-08 18:06:41 +02:00
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
2015-07-31 22:16:11 +02:00
2016-10-08 18:06:41 +02:00
PRODUCTS = [ ( " Black Widow " , PRODUCT_ID_BLACK_WIDOW ) ,
( " Black Widow Ultimate " , PRODUCT_ID_BLACK_WIDOW_ULTIMATE ) ,
( " Black Widow 2013/2014 " , PRODUCT_ID_BLACK_WIDOW_2013 ) ]
2015-07-31 22:16:11 +02:00
2016-10-08 18:06:41 +02:00
LOG = sys . stderr . write
class BlackWidow ( object ) :
2015-07-31 22:16:11 +02:00
def __init__ ( self ) :
2016-10-08 18:06:41 +02:00
self . kernel_driver_detached = False
self . interface_claimed = False
self . detected_keyboard = None
2015-07-31 22:16:11 +02:00
2016-10-08 18:06:41 +02:00
self . find_device ( )
if self . device_found ( ) :
self . claim_interface ( )
2015-07-31 22:16:11 +02:00
2016-10-08 18:06:41 +02:00
def __del__ ( self ) :
if self . interface_claimed :
self . release_interface ( )
2016-07-19 17:24:00 +02:00
2016-10-08 18:06:41 +02:00
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
2015-07-31 22:16:11 +02:00
2016-10-08 18:06:41 +02:00
def device_found ( self ) :
return self . device is not None
2015-07-31 22:16:11 +02:00
2016-10-08 18:06:41 +02:00
def claim_interface ( self ) :
try :
if self . device . is_kernel_driver_active ( USB_INTERFACE ) :
LOG ( " Kernel driver active; detaching it \n " )
2020-03-28 17:18:15 +01:00
self . device . detach_kernel_driver ( USB_INTERFACE )
2016-10-08 18:06:41 +02:00
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 :
2015-07-31 22:16:11 +02:00
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 )
2016-10-08 18:06:41 +02:00
def format_command ( self , command ) :
2015-07-31 22:16:11 +02:00
from functools import reduce
2016-10-08 18:06:41 +02:00
c1 = bytes . fromhex ( command )
2015-07-31 22:16:11 +02:00
c2 = [ reduce ( int . __xor__ , c1 ) ]
b = [ 0 ] * 90
b [ 5 : 5 + len ( c1 ) ] = c1
b [ - 2 : - 1 ] = c2
return bytes ( b )
2016-10-08 18:06:41 +02:00
def send ( self , command ) :
def internal_send ( msg ) :
USB_BUFFER = self . format_command ( command )
2015-07-31 22:16:11 +02:00
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 :
2016-08-28 21:32:45 +02:00
sys . stderr . write ( " Could not send data \n " )
2015-07-31 22:16:11 +02:00
if result == len ( USB_BUFFER ) :
2016-08-28 21:32:45 +02:00
LOG ( " Data sent successfully \n " )
2015-07-31 22:16:11 +02:00
return result
2016-10-08 18:06:41 +02:00
if isinstance ( command , list ) :
for i in command :
2015-07-31 22:16:11 +02:00
print ( ' >> {} \n ' . format ( i ) )
2016-10-08 18:06:41 +02:00
internal_send ( i )
elif isinstance ( command , str ) :
internal_send ( command )
2015-07-31 22:16:11 +02:00
def main ( ) :
2016-10-08 18:06:41 +02:00
#
2016-07-19 17:24:00 +02:00
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 '
2015-07-31 22:16:11 +02:00
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 " )
2016-07-19 17:24:00 +02:00
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) " )
2015-07-31 22:16:11 +02:00
( options , args ) = parser . parse_args ( )
2016-07-19 17:24:00 +02:00
2016-10-08 18:06:41 +02:00
bw = BlackWidow ( )
2015-07-31 22:16:11 +02:00
if bw . device_found ( ) :
2016-07-19 17:24:00 +02:00
# enable macro keys
2015-07-31 22:16:11 +02:00
if options . init == True :
LOG ( " Sending initiation command \n " )
bw . send ( init_old )
2016-07-19 17:24:00 +02:00
# set led status (doesn't work with BlackWidow 2016 yet)
2015-07-31 22:16:11 +02:00
if options . led == " unmodified " :
2016-07-19 17:24:00 +02:00
pass
2015-07-31 22:16:11 +02:00
elif options . led == " pulsate " :
LOG ( " Sending led pulsate command \n " )
bw . send ( pulsate )
elif options . led == " bright " :
2016-08-28 21:32:45 +02:00
LOG ( " Sending no pulsate command and led bright command \n " )
bw . send ( no_pulsate )
bw . send ( brightness + ' FF ' )
2015-07-31 22:16:11 +02:00
elif options . led == " normal " :
2016-08-28 21:32:45 +02:00
LOG ( " Sending no pulsate command and led normal command \n " )
bw . send ( no_pulsate )
bw . send ( brightness + ' a8 ' )
2015-07-31 22:16:11 +02:00
elif options . led == " dim " :
2016-08-28 21:32:45 +02:00
LOG ( " Sending no pulsate command and led dim command \n " )
bw . send ( no_pulsate )
bw . send ( brightness + ' 54 ' )
2015-07-31 22:16:11 +02:00
elif options . led == " off " :
2016-08-28 21:32:45 +02:00
LOG ( " Sending no pulsate command and led off command \n " )
bw . send ( no_pulsate )
bw . send ( brightness + ' 00 ' )
2016-07-19 17:24:00 +02:00
else :
try :
brightness_level = int ( options . led )
if brightness_level > = 0 and brightness_level < = 0xFF :
2016-08-28 21:32:45 +02:00
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 : ] )
2016-07-19 17:24:00 +02:00
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 " :
2016-08-28 21:32:45 +02:00
LOG ( " Sending blacklight command \n " )
2016-07-19 17:24:00 +02:00
bw . send ( backlight + ' FF ' )
elif options . backlight == " normal " :
2016-08-28 21:32:45 +02:00
LOG ( " Sending blacklight command \n " )
2016-07-19 17:24:00 +02:00
bw . send ( backlight + ' a8 ' )
elif options . backlight == " dim " :
2016-08-28 21:32:45 +02:00
LOG ( " Sending blacklight command \n " )
2016-07-19 17:24:00 +02:00
bw . send ( backlight + ' 54 ' )
elif options . backlight == " off " :
2016-08-28 21:32:45 +02:00
LOG ( " Sending blacklight command \n " )
2016-07-19 17:24:00 +02:00
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
2016-08-28 21:32:45 +02:00
elif options . gamemode == " on " :
2016-07-19 17:24:00 +02:00
bw . send ( gamemode + ' 01 ' )
elif options . gamemode == " off " :
bw . send ( gamemode + ' 00 ' )
2015-07-31 22:16:11 +02:00
else :
2016-07-19 17:24:00 +02:00
LOG ( " Specified value for game mode is unknown and will be ignored. \n " )
2015-07-31 22:16:11 +02:00
2016-10-08 18:06:41 +02:00
else :
LOG ( " No device found \n " )
2015-07-31 22:16:11 +02:00
if __name__ == ' __main__ ' :
2016-07-19 17:24:00 +02:00
main ( )