You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

205 lines
6.6 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. #!/usr/bin/python3
  2. from optparse import OptionParser
  3. import usb
  4. import sys
  5. USB_REQUEST_TYPE = 0x21 # Host To Device | Class | Interface
  6. USB_REQUEST = 0x09 # SET_REPORT
  7. USB_VALUE = 0x0300
  8. USB_INDEX = 0x2
  9. USB_INTERFACE = 2
  10. VENDOR_ID = 0x1532 # Razer
  11. PRODUCT_ID_BLACK_WIDOW = 0x010e # BlackWidow
  12. PRODUCT_ID_BLACK_WIDOW_ULTIMATE = 0x011a # BlackWidow Ultimate
  13. PRODUCT_ID_BLACK_WIDOW_ULTIMATE_2012 = 0x010d # BlackWidow Ultimate 2012
  14. PRODUCT_ID_BLACK_WIDOW_2013 = 0x011b # BlackWidow 2013/2014
  15. PRODUCTS = [("Black Widow", PRODUCT_ID_BLACK_WIDOW),
  16. ("Black Widow Ultimate", PRODUCT_ID_BLACK_WIDOW_ULTIMATE),
  17. ("Black Widow Ultimate 2012", PRODUCT_ID_BLACK_WIDOW_ULTIMATE_2012),
  18. ("Black Widow 2013/2014", PRODUCT_ID_BLACK_WIDOW_2013)]
  19. LOG=sys.stderr.write
  20. class BlackWidow(object):
  21. def __init__(self):
  22. self.kernel_driver_detached = False
  23. self.interface_claimed = False
  24. self.detected_keyboard = None
  25. self.find_device()
  26. if self.device_found():
  27. self.claim_interface()
  28. def __del__(self):
  29. if self.interface_claimed:
  30. self.release_interface()
  31. def find_device(self):
  32. for product_name, product_id in PRODUCTS:
  33. self.device = usb.core.find(idVendor=VENDOR_ID, idProduct=product_id)
  34. if self.device:
  35. detected_keyboard = product_id
  36. LOG("Found device: %s\n" % product_name)
  37. break
  38. def device_found(self):
  39. return self.device is not None
  40. def claim_interface(self):
  41. try:
  42. if self.device.is_kernel_driver_active(USB_INTERFACE):
  43. LOG("Kernel driver active; detaching it\n")
  44. self.device.detach_kernel_driver(USB_INTERFACE)
  45. self.kernel_driver_detached = True
  46. LOG("Claiming interface\n")
  47. usb.util.claim_interface(self.device, USB_INTERFACE)
  48. self.interface_claimed = True
  49. except:
  50. LOG("Unable to claim interface. Ensure the script is running as root.\n")
  51. raise
  52. def release_interface(self):
  53. if self.interface_claimed:
  54. LOG("Releasing claimed interface\n")
  55. usb.util.release_interface(self.device, USB_INTERFACE)
  56. if self.kernel_driver_detached:
  57. LOG("Reattaching the kernel driver\n")
  58. self.device.attach_kernel_driver(USB_INTERFACE)
  59. def format_command(self, command):
  60. from functools import reduce
  61. c1 = bytes.fromhex(command)
  62. c2 = [ reduce(int.__xor__, c1) ]
  63. b = [0] * 90
  64. b[5:5+len(c1)] = c1
  65. b[-2:-1] = c2
  66. return bytes(b)
  67. def send(self, command):
  68. def internal_send(msg):
  69. USB_BUFFER = self.format_command(command)
  70. result = 0
  71. try:
  72. result = self.device.ctrl_transfer(USB_REQUEST_TYPE, USB_REQUEST, wValue=USB_VALUE, wIndex=USB_INDEX, data_or_wLength=USB_BUFFER)
  73. except:
  74. sys.stderr.write("Could not send data\n")
  75. if result == len(USB_BUFFER):
  76. LOG("Data sent successfully\n")
  77. return result
  78. if isinstance(command, list):
  79. for i in command:
  80. print(' >> {}\n'.format(i))
  81. internal_send(i)
  82. elif isinstance(command, str):
  83. internal_send(command)
  84. def main():
  85. #
  86. init_new = '0200 0403'
  87. init_old = '0200 0402'
  88. no_pulsate = '0303 0201 0400'
  89. pulsate = '0303 0201 0402'
  90. brightness = '0303 0301 04'
  91. backlight = '0303 0301 05'
  92. gamemode = '0303 0001 08'
  93. usage = "usage: %prog [options]"
  94. parser = OptionParser(usage=usage)
  95. parser.add_option("-i", "--init", action="store_true", dest="init", default=False, help="initiates the functionality of macro keys")
  96. 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")
  97. 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)")
  98. parser.add_option("-g", "--set-game-mode", type="string", dest="gamemode", default="unmodified", help="sets whether the game mode is enabled (on/off)")
  99. (options, args) = parser.parse_args()
  100. bw = BlackWidow()
  101. if bw.device_found():
  102. # enable macro keys
  103. if options.init == True:
  104. LOG("Sending initiation command\n")
  105. bw.send(init_old)
  106. # set led status (doesn't work with BlackWidow 2016 yet)
  107. if options.led == "unmodified":
  108. pass
  109. elif options.led == "pulsate":
  110. LOG("Sending led pulsate command\n")
  111. bw.send(pulsate)
  112. elif options.led == "bright":
  113. LOG("Sending no pulsate command and led bright command\n")
  114. bw.send(no_pulsate)
  115. bw.send(brightness + 'FF')
  116. elif options.led == "normal":
  117. LOG("Sending no pulsate command and led normal command\n")
  118. bw.send(no_pulsate)
  119. bw.send(brightness + 'a8')
  120. elif options.led == "dim":
  121. LOG("Sending no pulsate command and led dim command\n")
  122. bw.send(no_pulsate)
  123. bw.send(brightness + '54')
  124. elif options.led == "off":
  125. LOG("Sending no pulsate command and led off command\n")
  126. bw.send(no_pulsate)
  127. bw.send(brightness + '00')
  128. else:
  129. try:
  130. brightness_level = int(options.led)
  131. if brightness_level >= 0 and brightness_level <= 0xFF:
  132. LOG("Sending no pulsate command and led command with custom brightness\n")
  133. bw.send(no_pulsate)
  134. bw.send(brightness + "{0:#0{1}x}".format(brightness_level,4)[2:])
  135. else:
  136. raise ValueError
  137. except ValueError:
  138. LOG("Specified value for led status \"%s\" is unknown and will be ignored.\n" % options.led)
  139. # experimental: set backlight (BlackWidow 2016 only)
  140. if options.backlight == "unmodified":
  141. pass
  142. else:
  143. if False: # condition for BlackWidow 2016
  144. if options.backlight == "bright":
  145. LOG("Sending blacklight command\n")
  146. bw.send(backlight + 'FF')
  147. elif options.backlight == "normal":
  148. LOG("Sending blacklight command\n")
  149. bw.send(backlight + 'a8')
  150. elif options.backlight == "dim":
  151. LOG("Sending blacklight command\n")
  152. bw.send(backlight + '54')
  153. elif options.backlight == "off":
  154. LOG("Sending blacklight command\n")
  155. bw.send(backlight + '00')
  156. else:
  157. try:
  158. brightness_level = int(options.backlight)
  159. if brightness_level >= 0 and brightness_level <= 0xFF:
  160. bw.send(brightness + "{0:#0{1}x}".format(brightness_level,4)[2:])
  161. else:
  162. raise ValueError
  163. except ValueError:
  164. LOG("Specified value for backlight status \"%s\" is unknown and will be ignored.\n" % options.led)
  165. else:
  166. LOG("Setting the backlight is not supported by the detected keyboard.")
  167. # enable/disable game mode
  168. if options.gamemode == "unmodified":
  169. pass
  170. elif options.gamemode == "on":
  171. bw.send(gamemode + '01')
  172. elif options.gamemode == "off":
  173. bw.send(gamemode + '00')
  174. else:
  175. LOG("Specified value for game mode is unknown and will be ignored.\n")
  176. else:
  177. LOG("No device found\n")
  178. if __name__ == '__main__':
  179. main()