53

I picked up a Razer BlackWidow Ultimate that has additional keys meant for macros that are set using a tool that's installed on Windows. I'm assuming that these aren't some fancypants joojoo keys and should emit scancodes like any other keys.

Firstly, is there a standard way to check these scancodes in Linux? Secondly, how do I set these keys to do things in command line and X-based Linux setups? My current Linux install is Xubuntu 10.10, but I'll be switching to Kubuntu once I have a few things fixed up. Ideally the answer should be generic and system-wide.

Things I have tried so far:

Things I need to try

  • snoopy pro + reverse engineering (oh dear)

  • Wireshark - preliminary futzing around seems to indicate no scancodes emitted when what I seem to think is the keyboard is monitored and keys pressed. Might indicate additional keys are a separate device or need to be initialised somehow.

  • Need to cross reference that with lsusb output from Linux, in three scenarios: standalone, passed through to a Windows VM without the drivers installed, and the same with.

  • LSUSB only detects one device on a standalone Linux install

  • It might be useful to check if the mice use the same Razer Synapse driver , since that means some variation of razercfg might work (not detected, only seems to work for mice)

Things I have worked out:

  • In a Windows system with the driver, the keyboard is seen as a keyboard and a pointing device. The pointing device uses - in addition to your bog standard mouse drivers - a driver for something called a Razer Synapse.

  • Mouse driver seen in Linux under evdev and lsusb as well

  • Single device under OS X apparently, though I have yet to try lsusb equivalent on that

  • Keyboard goes into pulsing backlight mode in OS X upon initialisation with the driver. This should probably indicate that there's some initialisation sequence sent to the keyboard on activation.

  • They are, in fact, fancypants joojoo keys.

Extending this question a little:

I have access to a Windows system so if I need to use any tools on that to help answer the question, it's fine. I can also try it on systems with and without the config utility. The expected end result is still to make those keys usable on Linux however.

I also realise this is a very specific family of hardware. I would be willing to test anything that makes sense on a Linux system if I have detailed instructions - this should open up the question to people who have Linux skills, but no access to this keyboard.

The minimum end result I require:

I need these keys detected, and usable in any fashion on any of the current graphical mainstream Ubuntu variants, and naturally have to work with my keyboard. Virtual cookie and mad props if it's something nicely packaged and usable by the average user.

I will require compiled code that will work on my system, or a source that I can compile (with instructions if it's more complex than ./configure , make, make install) if additional software not on the Ubuntu repositories for the current LTS or standard desktop release at the time of the answer. I will also require sufficient information to replicate, and successfully use the keys on my own system.

Robotnik
  • 2,645
Journeyman Geek
  • 133,878

6 Answers6

45

M1-M5 are in fact regular keys - they just need to be specifically enabled before pressing them will generate a scancode. tux_mark_5 developed a small Haskell program which sends the correct SET_REPORT message to Razer keyboards to enable these keys, and ex-parrot ported the same code to Python.

On Arch Linux systems the Python port has been packaged and is available from https://aur.archlinux.org/packages.php?ID=60518.

On Debian or Ubuntu systems setting up the Python port of the code is relatively easy. You need to install PyUSB and libusb (as root):

    aptitude install python-usb

Then grab the blackwidow_enable.py file from http://finch.am/projects/blackwidow/ and execute it (also as root):

    chmod +x blackwidow_enable.py
    ./blackwidow_enable.py

This will enable the keys until the keyboard is unplugged or the machine is rebooted. To make this permanent call the script from whatever style of startup script you most prefer. For instructions on how to set this up in Debian have a look at the Debian documentation.

To use tux_mark_5's Haskell code you'll need to install Haskell and compile the code yourself. These instructions are for a Debian-like system (including Ubuntu).

  1. Install GHC, libusb-1.0-0-dev and cabal (as root):

    aptitude install ghc libusb-1.0-0-dev cabal-install git pkg-config
    
  2. Fetch the list of packages:

    cabal update
    
  3. Install USB bindings for Haskell (no need for root):

    cabal install usb
    
  4. Download the utility:

    git clone git://github.com/tuxmark5/EnableRazer.git
    
  5. Build the utility:

    cabal configure
    cabal build
    
  6. Run the utility (also as root):

    ./dist/build/EnableRazer/EnableRazer
    

After this you can copy EnableRazer binary anywhere you want and run it at startup.

Immediately after execution, X server should see M1 as XF86Tools, M2 as XF86Launch5, M3 as XF86Launch6, M4 as XF86Launch7 and M5 as XF86Launch8. Events for FN are emitted as well.

These keys can be bound within xbindkeys or KDE's system settings to arbitrary actions.

Since your keyboard might be different, you might need to change the product ID in Main.hs line 64:

withDevice 0x1532 0x<HERE GOES YOUR KEYBOARD's PRODUCT ID> $ \dev -> do
tux_mark_5
  • 1,066
  • 8
  • 3
24

Razer seems to be forcing their cloud-based Synapse 2 configurator on all users nowadays, with accompanying firmware upgrade to version 2.*. Once you have upgraded the firmware, you cannot go back (keyboard is completely bricked if you try to flash it with older firmware).

The ‘magic bytes’ from the Haskell program in tux_mark_5's answer won't work with the latest firmware. Instead, the driver sends these bytes during the initialization sequence: ‘0200 0403’. These enable the macro keys, but the keyboard enters a peculiar mode in which instead of the standard HID protocol it sends 16-byte packets (presumably to increase the number of keys that can be pressed simultaneously). Linux HID system cannot quite cope with this, and while most keys work as expected, the macro keys stay unrecognized: the HID driver doesn't feed any data to the input layer when they are pressed.

To make your keyboard enter the legacy mode (in which the macro keys send XF86Launch* keycodes, and the FN key sends keycode 202), send these bytes: 0200 0402.

The full packet will be:

00000000 00020004 02000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
00000000 00000000 0400

Here's a very rough and dirty program I wrote in less esoteric Python 3 to perform the task. Note the code to generate the Razer control packets in blackwidow.bwcmd() and the Razer logo LED commands as a bonus :)

#!/usr/bin/python3

import usb
import sys

VENDOR_ID = 0x1532  # Razer
PRODUCT_ID = 0x010e  # BlackWidow / BlackWidow Ultimate

USB_REQUEST_TYPE = 0x21  # Host To Device | Class | Interface
USB_REQUEST = 0x09  # SET_REPORT

USB_VALUE = 0x0300
USB_INDEX = 0x2
USB_INTERFACE = 2

LOG = sys.stderr.write

class blackwidow(object):
  kernel_driver_detached = False

  def __init__(self):
    self.device = usb.core.find(idVendor=VENDOR_ID, idProduct=PRODUCT_ID)

    if self.device is None:
      raise ValueError("Device {}:{} not found\n".format(VENDOR_ID, PRODUCT_ID))
    else:
      LOG("Found device {}:{}\n".format(VENDOR_ID, PRODUCT_ID))

    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)

  def __del__(self):
    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)

    LOG("Done.\n")

  def bwcmd(self, c):
    from functools import reduce
    c1 = bytes.fromhex(c)
    c2 = [ reduce(int.__xor__, c1) ]
    b = [0] * 90
    b[5: 5+len(c1)] = c1
    b[-2: -1] = c2
    return bytes(b)

  def send(self, c):
    def _send(msg):
      USB_BUFFER = self.bwcmd(msg)
      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(c, list):
      #import time
      for i in c:
        print(' >> {}\n'.format(i))
        _send(i)
        #time.sleep(.05)
    elif isinstance(c, str):
        _send(c)

###############################################################################

def main():
    init_new  = '0200 0403'
    init_old  = '0200 0402'
    pulsate = '0303 0201 0402'
    bright  = '0303 0301 04ff'
    normal  = '0303 0301 04a8'
    dim     = '0303 0301 0454'
    off     = '0303 0301 0400'

    bw = blackwidow()
    bw.send(init_old)

if __name__ == '__main__':
    main()
Sergey
  • 341
8

Perhaps this might shed some light on the issue (from the showkey manpage):

In 2.6 kernels raw mode, or scancode mode, is not very raw at all. Scan codes are first translated to key codes, and when scancodes are desired, the key codes are translated back. Various transformations are involved, and there is no guarantee at all that the final result corresponds to what the keyboard hardware did send. So, if you want to know the scan codes sent by various keys it is better to boot a 2.4 kernel. Since 2.6.9 there also is the boot option atkbd.softraw=0 that tells the 2.6 kernel to return the actual scan codes.

The raw scan codes are available only on AT and PS/2 keyboards, and even then they are disabled unless the atkbd.softraw=0 kernel parameter is used. When the raw scan codes are not available, the kernel uses a fixed built-in table to produce scan codes from keycodes. Thus, setkeycodes(8) can affect the output of showkey in scan code dump mode.

I'm about to see if showkey will dump anything with the macro keys after this boot option is set.

EDIT: After the reboot, no success, but I was looking into capturing raw input from the USB devices themselves. I noted the following, interestingly (I have a Razer Diamondback as well as BlackWidow):

[root@kestrel by-id]# pwd
/dev/input/by-id
[root@kestrel by-id]# ls
usb-Razer_Razer_BlackWidow_Ultimate-event-kbd    usb-Razer_Razer_Diamondback_Optical_Mouse-event-mouse
usb-Razer_Razer_BlackWidow_Ultimate-event-mouse  usb-Razer_Razer_Diamondback_Optical_Mouse-mouse
usb-Razer_Razer_BlackWidow_Ultimate-mouse
[root@kestrel by-id]#

However, using dd to capture raw input works on both diamondback mice, on the event-kbd device, but not on the BlackWidow mouse devices.

I'm guessing perhaps they do not generate any output until somehow activated by the drivers that are installed. I don't know much about Linux USB however, so I don't even know if this makes sense. Perhaps they need to be bound first?

Well, all three black widow devices are noted in /proc/bus/input/devices, however they don't appear to be enumerated in lsusb or /proc/bus/usb/devices. I'm not sure how to access these devices to attempt to bind them or interface with them in any way.

event4 seems to correspond to the actual keyboard, event6 with the macro keys, but I still can't capture any input from them. Hope that all helped.

   [root@kestrel input]# ls
devices  handlers
[root@kestrel input]# cat handlers
N: Number=0 Name=kbd
N: Number=1 Name=mousedev Minor=32
N: Number=2 Name=evdev Minor=64
N: Number=3 Name=rfkill
[root@kestrel input]# pwd
/proc/bus/input
[root@kestrel input]# cat devices
I: Bus=0019 Vendor=0000 Product=0001 Version=0000
N: Name="Power Button"
P: Phys=PNP0C0C/button/input0
S: Sysfs=/devices/LNXSYSTM:00/LNXSYBUS:00/PNP0C0C:00/input/input0
U: Uniq=
H: Handlers=kbd event0 
B: EV=3
B: KEY=10000000000000 0

I: Bus=0019 Vendor=0000 Product=0001 Version=0000
N: Name="Power Button"
P: Phys=LNXPWRBN/button/input0
S: Sysfs=/devices/LNXSYSTM:00/LNXPWRBN:00/input/input1
U: Uniq=
H: Handlers=kbd event1 
B: EV=3
B: KEY=10000000000000 0

I: Bus=0017 Vendor=0001 Product=0001 Version=0100
N: Name="Macintosh mouse button emulation"
P: Phys=
S: Sysfs=/devices/virtual/input/input2
U: Uniq=
H: Handlers=mouse0 event2 
B: EV=7
B: KEY=70000 0 0 0 0
B: REL=3

I: Bus=0003 Vendor=1532 Product=010d Version=0111
N: Name="Razer Razer BlackWidow Ultimate"
P: Phys=usb-0000:00:12.1-3/input0
S: Sysfs=/devices/pci0000:00/0000:00:12.1/usb4/4-3/4-3:1.0/input/input4
U: Uniq=
H: Handlers=kbd event4 
B: EV=120013
B: KEY=1000000000007 ff9f207ac14057ff febeffdfffefffff fffffffffffffffe
B: MSC=10
B: LED=7

I: Bus=0003 Vendor=1532 Product=010d Version=0111
N: Name="Razer Razer BlackWidow Ultimate"
P: Phys=usb-0000:00:12.1-3/input1
S: Sysfs=/devices/pci0000:00/0000:00:12.1/usb4/4-3/4-3:1.1/input/input5
U: Uniq=
H: Handlers=kbd event5 
B: EV=1f
B: KEY=837fff002c3027 bf00444400000000 1 c040a27c000 267bfad941dfed 9e000000000000 0
B: REL=40
B: ABS=100000000
B: MSC=10

I: Bus=0003 Vendor=1532 Product=010d Version=0111
N: Name="Razer Razer BlackWidow Ultimate"
P: Phys=usb-0000:00:12.1-3/input2
S: Sysfs=/devices/pci0000:00/0000:00:12.1/usb4/4-3/4-3:1.2/input/input6
U: Uniq=
H: Handlers=mouse2 event6 
B: EV=17
B: KEY=70000 0 0 0 0
B: REL=103
B: MSC=10

I: Bus=0003 Vendor=1532 Product=0002 Version=0110
N: Name="Razer Razer Diamondback Optical Mouse"
P: Phys=usb-0000:00:12.1-2/input0
S: Sysfs=/devices/pci0000:00/0000:00:12.1/usb4/4-2/4-2:1.0/input/input9
U: Uniq=
H: Handlers=mouse1 event3 
B: EV=17
B: KEY=7f0000 0 0 0 0
B: REL=103
B: MSC=10

[root@kestrel input]# 
7

My solution is for Razer BlackWidow 2013 Mechanical Gaming Keyboard (Model Number: RZ03-0039) and was tested on openSUSE 12.3.

I used Google Translate on this link.

Basically it uses the modified version of @Sergey's answer for this question, but with simple modifications:

  1. My PRODUCT_ID = 0x011b

  2. On my openSUSE 12.3, python-usb is not available for Python 3, so I converted this script to work with Python 2 by removing the bwcmd method and defined the USB_BUFFER = ... as in the link from @tux_mark_5's answer.


For convenience here is the content of my /usr/local/sbin/init_blackwidow.py:

#!/usr/bin/python

"""This is a patched version of Sergey's code form
https://superuser.com/a/474595/8647

It worked for my Razer BlackWidow 2013 Mechanical Gaming Keyboard
(Model Number: RZ03-0039).

"""
import usb
import sys

VENDOR_ID = 0x1532       # Razer
PRODUCT_ID = 0x011b      # BlackWidow 2013 Mechanical Gaming Keyboard

USB_REQUEST_TYPE = 0x21  # Host To Device | Class | Interface
USB_REQUEST = 0x09       # SET_REPORT

USB_VALUE = 0x0300
USB_INDEX = 0x2
USB_INTERFACE = 2

USB_BUFFER = b"\x00\x00\x00\x00\x00\x02\x00\x04\x02\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00"

LOG = sys.stderr.write


class blackwidow(object):
    kernel_driver_detached = False

    def __init__(self):
        self.device = usb.core.find(idVendor=VENDOR_ID, idProduct=PRODUCT_ID)

        if self.device is None:
            raise ValueError("Device {}:{} not found\n".format(VENDOR_ID, PRODUCT_ID))
        else:
            LOG("Found device {}:{}\n".format(VENDOR_ID, PRODUCT_ID))

        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)

    def __del__(self):
        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)

        LOG("Done.\n")

    def send(self, c):
        def _send(msg):
            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(c, list):
            for i in c:
                print(' >> {}\n'.format(i))
                _send(i)
        elif isinstance(c, str):
            _send(c)


def main():
    init_new = '0200 0403'
    init_old = '0200 0402'
    pulsate  = '0303 0201 0402'
    bright   = '0303 0301 04ff'
    normal   = '0303 0301 04a8'
    dim      = '0303 0301 0454'
    off      = '0303 0301 0400'

    bw = blackwidow()
    bw.send(init_old)


if __name__ == '__main__':
    main()

... and my /etc/udev/rules.d/99-razer-balckwidow.rules is:

SUBSYSTEM=="usb", ACTION=="add", ATTR{idVendor}=="1532", ATTR{idProduct}=="011b", RUN+="/usr/local/sbin/init_blackwidow.py"
Chen Levy
  • 1,685
2

Maybe this document will help you:

The Linux keyboard and console HOWTO, Useful programs

ascobol
  • 929
1

See Razer Key Mapper for Linux.

This works with all Razer device's macros, given some code modification. If you still don't have a solution and your device isn't listed I'd be happy to help you configure your device and add it to my supported list.