Linux on Mac hardware: How to remove caps lock delay on Apple MacBook Pro aluminum keyboard when booted to Linux

I've figured out how to do this. In short, you must send a "Feature Report" consisting of the bytes 0x9, 0x0, 0x0, 0x0 to the appropriate hidraw device as root.

You can find the right hidraw device with this command:

dmesg | grep Apple | grep Keyboard | grep input0 | tail -1 | sed -e 's/.*hidraw\([[:digit:]]\+\).*/\/dev\/hidraw\1/'

The code to send the magic control packet is below. Compiles with gcc, takes the hidraw device as parameter. So the entire flow is:

  1. save the code below as disable-capslock-delay.c
  2. gcc -o disable-capslock-delay disable-capslock-delay.c
  3. HIDDEVICE=$(dmesg | grep Apple | grep Keyboard | grep input0 | tail -1 | sed -e 's/.*hidraw\([[:digit:]]\+\).*/\/dev\/hidraw\1/')
  4. sudo ./disable-capslock-delay $HIDDEVICE

Steps 3 and 4 have to be done every time you reboot (or unplug and re-plug the keyboard); you can put them into /etc/rc.local (or your distro's equivalent) to execute them at boot (you don't need sudo in that case; and you might want to move the compiled binary into /usr/local/sbin/ or something).

I've put in some safety checks for vendor ID, device ID, and report descriptor length. You may have to change the latter two if your model differs from mine.


#include <linux/hidraw.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) { if (argc != 2 || strcmp(argv[1], "-h") == 0) { printf("Pass a hidraw device as the first and only parameter!\n"); printf("You may find the right device with:\n"); printf(" dmesg | grep Apple | grep Keyboard | grep input0 | tail -1 | " "sed -e 's/.hidraw\([[:digit:]]\+\)./\/dev\/hidraw\1/'\n"); return 1; } int fd, i, res, desc_size = 0; char buf[256]; struct hidraw_devinfo info; char *device = argv[1]; fd = open(device, O_RDWR | O_NONBLOCK); if (fd < 0) { perror("Unable to open device"); return 1; } memset(&info, 0, sizeof(info)); memset(buf, 0, sizeof(buf)); // Get Report Descriptor Size res = ioctl(fd, HIDIOCGRDESCSIZE, &desc_size); if (res < 0) { perror("HIDIOCGRDESCSIZE"); } if (desc_size != 75) { printf("Error: unexpected descriptor size %d; you've probably got " "the wrong hidraw device!\n", desc_size); return 1; } // Get Raw Info res = ioctl(fd, HIDIOCGRAWINFO, &info); if (res < 0) { perror("HIDIOCGRAWINFO"); } else { if (info.vendor != 0x05ac) { printf("Error: Wrong vendor ID, make sure you got the right " "hidraw device!\n"); return 1; } if (info.product != 0x0250) { printf("Warning: Unknown product ID 0x%x!\n", info.product); } } // Get Feature buf[0] = 0x09; // Report Number res = ioctl(fd, HIDIOCGFEATURE(256), buf); if (res < 0) { perror("HIDIOCGFEATURE"); } else { printf("HID Feature Report (before change):\n\t"); for (i = 0; i < res; i++) printf("%hhx ", buf[i]); puts("\n"); } // Set Feature buf[0] = 0x09; // Report Number buf[1] = 0x00; // Report data buf[2] = 0x00; // padding buf[3] = 0x00; // padding res = ioctl(fd, HIDIOCSFEATURE(4), buf); if (res < 0) { perror("HIDIOCSFEATURE"); } else { printf("Caps lock delay disabled.\n"); } // Get Feature buf[0] = 0x09; // Report Number res = ioctl(fd, HIDIOCGFEATURE(256), buf); if (res < 0) { perror("HIDIOCGFEATURE"); } else { printf("HID Feature Report (after change):\n\t"); for (i = 0; i < res; i++) printf("%hhx ", buf[i]); puts("\n"); } close(fd); return 0; }


Here is the fix for macOS Sierra.

Go to System Preferences > Accessibility

When the accessibility window is open — on the left part of the window – click Keyboard

Then there will be the 2 options — for sticky keys, and for slow keys — click the box next to slow keys to enable it — then click the Options... button – a new window will come up with a slider to change the acceptance delay — by default this is in the middle. Slide the button all the way to the left, so that it is the shortest time possible.

Now it should be fixed. Have not experimented to see if the fix stays upon restart, but I’m hopeful that it will.


EDIT: This seems to be a popular landing question for users looking to remove the delay on the caps lock key in OS X. As of OS X Mojave,

Go to System Preferences; Accessibility; Keyboard; enable Slow Keys and go into options; turn the delay down to the minimum. The only unwanted side-effect I've noticed so far is slow backspace action when holding it down. Generally I use CMD+A / CMD+SHIFT+L/R / CMD+SHIFT+CTRL+L/R anyway so it's not a big issue.

As of El Capitan and earlier,

The solution is quite elusive, and you wouldn't really know you removed it unless you were specifically trying to get rid of it in the first place. The way I am going to show you is purely for the (current)latest version of OSX, Yosemite. However you can absolutely apply this method to previous and future versions.

The solution is simple. If you navigate to Keyboard in the System preferences via the apple logo in the top left hand corner you will reach this screen

enter image description here

If you click the modifier keys button, you are able to change the function of each of the keys which are programmable. All you have to do is set the caps lock key to no action and press ok to bring you back to the keyboard menu. Once done, go back into the modifier keys and change the caps lock key back to caps lock and this will remove the delay! Do note that this fix remains in place until you sleep, restart or power down the device. At which point the delay is reinstated.

They're strange fixes, and it begs the question why do they not provide an option to remove the delay when this fix is purely software based. But hey, at least there is a way!

Happy capsing.


Elaborating on @jmrk answer. His code works well for the so called "aluminum" keyboard, but apple also released wireless keyboards, under the name "magic keyboard", with product id 0x022d. (They also have aluminum chassis, and I think that my model is from 2008 or so.) This product has the same caps lock issue, and almost the same code can fix it. Beside changing the product id, the descriptor size is 218, and the data to send is not 0x00, but 0x01:

buf[0] = 0x09;  // Report Number
buf[1] = 0x01;  // Report data

I found this today and thought to record my answer here in case it helps anyone, including my future self.

Here is a complete program fixing the issue on both kind of devices. You can pass extra hid devices, it'll apply only to known ones. So you can simply call it as ./a.out /dev/hidraw*

#include <linux/hidraw.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv) {
  printf("Pass all devices to configure. For example, like this: %s /dev/hidraw*\n", argv[0]);
  int expected_desc_size;
  char data_to_set;
  for (int argnum=1;argnum<argc;argnum++) {
    printf("Trying to configure %s...\n", argv[argnum]);
    
    int fd, i, res, desc_size = 0;
    char buf[256];
    struct hidraw_devinfo info;
    char *device = argv[argnum];
    fd = open(device, O_RDWR | O_NONBLOCK);
    if (fd < 0) {
      perror("Unable to open device");
      return 1;
    }
    memset(&info, 0, sizeof(info));
    memset(buf, 0, sizeof(buf));
    
    // Get Raw Info
    res = ioctl(fd, HIDIOCGRAWINFO, &info);
    if (res < 0) {
      perror("HIDIOCGRAWINFO");
      return 1;
    }
    if (info.vendor == 0x05ac) {
      printf("Apple device found!\n");
    } else {
      printf("Not an apple device.\n");
      continue;
    }
    if (info.product == 0x022d) {
      printf("We have a 'magic' keyboard\n");
      expected_desc_size = 218;
      data_to_set = 1;
    } else if (info.product == 0x0250) {
      printf("We have an 'aluminium' keyboard\n");
      expected_desc_size = 75;
      data_to_set = 0;
    }
    else {
      printf("Warning: Unknown product ID 0x%x!\n", info.product);
      continue;
    }
    // Get Report Descriptor Size
    res = ioctl(fd, HIDIOCGRDESCSIZE, &desc_size);
    if (res < 0) {
      perror("HIDIOCGRDESCSIZE");
      return 1;
    }
    if (desc_size != expected_desc_size) {
      printf("Error: unexpected descriptor size %d; panic'ing", desc_size);
      return 1;
    }
    // Get Feature
    buf[0] = 0x09;  // Report Number
    res = ioctl(fd, HIDIOCGFEATURE(256), buf);
    if (res < 0) {
      perror("HIDIOCGFEATURE");
    } else {
      printf("HID Feature Report (before change):\n\t");
      for (i = 0; i < res; i++) printf("%hhx ", buf[i]);
      puts("\n");
    }
      
    // Set Feature
    buf[0] = 0x09;  // Report Number
    buf[1] = data_to_set;  // Report data
    buf[2] = 0x00;  // padding
    buf[3] = 0x00;  // padding
    res = ioctl(fd, HIDIOCSFEATURE(4), buf);
    if (res < 0) {
      perror("HIDIOCSFEATURE");
    } else {
      printf("Caps lock delay disabled.\n");
    }
    // Get Feature
    buf[0] = 0x09;  // Report Number
    res = ioctl(fd, HIDIOCGFEATURE(256), buf);
    if (res < 0) {
      perror("HIDIOCGFEATURE");
      return 1;
    } 
    printf("HID Feature Report (after change):\n\t");
    for (i = 0; i < res; i++) printf("%hhx ", buf[i]);
    puts("\n");
    close(fd);
    }
  return 0;
}

I do know for certain that the caps lock delay is a feature of the firmware on the keyboard itself - so you can be confident that the delay timing happens no matter what OS the keyboard is driving at the moment.

I also know that Apple does not offer a path to flash the firmware to a lower level so we all will have to wait until someone with enough hardware experience blogs the tools and steps they performed to trick the hardware into loading the older firmware (or provide what looks like a newer firmware that regresses the delay to how it worked before without any delay.)

I apologize for the non-answer, but the path to accomplish this is well lit and it may help others take action (selecting an older keyboard - or refraining from upgrading the firmware) in the mean time while we wait for a real solution to implement a short or no timer on the caps lock key.