Qmk_firmware: Caps Led Light not working in MacOS

Created on 23 Nov 2018  Â·  51Comments  Â·  Source: qmk/qmk_firmware

Using a DZ60
added following code in keymap.c and it work under window, but no luck under MacOS.

void led_set_user(uint8_t usb_led) {
  if (usb_led & (1<<USB_LED_CAPS_LOCK)) {
    PORTB &= ~(1 << 2);
  } else {
    PORTB |= (1 << 2);
  }
}

I think I have missed something, because the hex in http://qmkeyboard.cn is working without problem... (But I need advance feature... and unable to download to source code from that website)

bug help wanted

Most helpful comment

Yes, and that's also what it's doing, but the ifdef is wrong. The keyboard descriptor only has a report ID (and thus the host will give it back to you) when KEYBOARD_SHARED_EP = yes (and this implies SHARED_EP_ENABLE in case you have extrakey or NKRO off), so by changing it to #ifdef KEYBOARD_SHARED_EP, it works in both cases.

All 51 comments

@YCF I cannot see dz60 in that website ....

@Menchen you should not need to put that in your keymap.c if you're using the latest qmk_firmware. It is currently included in dz60.c.

I would recommend updating, and flashing a new hex file.

Tested with git pull and make again(No dz60 file got updated. Start building my conf last week with git clone ), also deleted that code in my keymap.c, and still not working. And even in windows it's not working...

Oh, I see what's wrong:
https://github.com/qmk/qmk_firmware/blob/master/keyboards/dz60/dz60.c#L17-L34

Try changing this to:

void led_set_kb(uint8_t usb_led) {
    if (usb_led & (1 << USB_LED_CAPS_LOCK)) {
        PORTB &= ~(1 << 2);
    } else {
        PORTB &= ~(1 << 2);
    }

    led_set_user(usb_led);
}

Edit the dz60.c file, compile, and see if that works.

Thought I would piggy-back onto this issue, as mine seems somewhat the same.
I'm attempting to add caps-lock led functionality to xd87. After 4 hours of pulling my hair out, I came across this issue here. I am also using a mac.
I don't have a PC, but led seems to work when plugged into a console.
Further google searches seems to highlight issues with qmk and osx when it comes to caps led.
Anyhow, here's my code. Anything I can do to?
https://github.com/WarmCatUK/qmk_firmware/blob/messingabout/keyboards/xd87/xd87.c

After replacing with
https://github.com/qmk/qmk_firmware/blob/master/keyboards/dz60/dz60.c#L24-L34
, the caps led turn on by default and unable to turn off (not working)

And if I replace and delete led_init_port(your typo?), as it is not inited so the led is off all time.

All above is tested on windows and macOs.

I think the correct code for windows/linux is what I posted in first post, but it doesn't work for mac, maybe because macOs use individual cap lock state per keyboard... and USB_LED_CAPS_LOCK or usb_led is not handled correctly...

Exactly, it’s something to do with macOS and individual caps lock state per keyboard; this isn’t handled by qmk.
I can plug a Filco in and it works no problem.
One workaround is to create a “caps” layer and turn the caps lock key into a function layer toggle. Not ideal though :-/

@WarmCatUK, with hex file I build with the website in post 1 cap key led is working fine under macos.... and as the name of website suggests, it use qmk.

@Menchen maybe it’s using an older version?
I do seem to remember my DZ60 caps lock working, as does some of my other keyboards with qmk... when I get the chance I’ll reflash a DZ60 with newest qmk.
For sure the problem is in qmk, however my experience is mainly hardware.

macOS deals wierdly with "non apple certified" hid devices when it comes to leds. could you guys make sure your flipping the correct pins that the led is connected on?

I dont have a keyboard with a capslock led on hand to test. But I will botch something together.

Overall my point is we are probably missing some magic. Could we make sure that ur keyboard works on windows and not on macOS?

@Menchen Sorry, that should be:

void led_set_kb(uint8_t usb_led) {
    if (usb_led & (1 << USB_LED_CAPS_LOCK)) {
        PORTB |= (1 << 2);
    } else {
        PORTB &= ~(1 << 2);
    }

    led_set_user(usb_led);
}

But the led initialization should remain the same. If this doesn't work as expected, then swap the portb calls, and that should work. Eg:

void led_set_kb(uint8_t usb_led) {
    if (usb_led & (1 << USB_LED_CAPS_LOCK)) {
        PORTB &= ~(1 << 2);
    } else {
        PORTB |= (1 << 2);
    }

    led_set_user(usb_led);
}

For reference, I'm pulling this information from here:
https://github.com/qmk/qmk_firmware/blob/master/keyboards/handwired/woodpad/keymaps/drashna/keymap.c#L74-L88

Which is an old macro pad of mine.

@drashna, the second one is working on windows. And not on macos, same as the code I posted on the issue.

@yiancar my keyboard is working under windows, read first post..

Okay. In that case, the firmware actually pulls this status from the host system. So if the host isn't sending this info to the keyboard, no amount of code change will fix that.
It may be worth turning the console on, and printing out the status, so you can see if it's actually registering or not.

@Menchen sorry about not reading first post...

@drashna im pretty sure u can trick macos into sending u the info. Ill have a look

@yiancar @drashna or we can store cap lock state in qmk and manually switch it's value with something like macro when the host is mac, as the default caps state is always off.

yea ofcource but thats "bad" as if you have 2 keyboards, the caps lock led will not sync between them.

I can confirm the bug btw and I am 99% its due to the mac being a... mac.

Let me dig into it a bit more before we fake the led :)

@yiancar In macos, the normal behaviour of caps lock is not synced..... Tested with normal keyboard and hex file in post one, the led do not sync.

did you check with a mac external keyboard?
ie 2 apple keyboards.

Could you also try this feature on karabinner?
https://github.com/tekezo/Karabiner-Elements/issues/1502#issuecomment-442004750

I did with external and internal. Also karabiner is not working well on Mojave, it lock my cap led state to on and unable to turn down(internal keyboard).

ok cool i have also confirmed with an apple person that the leds dont sync.

I think we cannot detect if its a mac or a pc (we have this problem with unicode). Could we create a special KC_CAPS keycode which overwrites the state of USB_LED_CAPS_LOCK?! @drashna ur expert input:)

@drashna @yiancar Can we check if it is macos by checking right after the caps key is pressed? If the usb_led change it's not macos, and if it doesn't it is macos.

If it work we could also exploit this bug to detect is the host is mac or not. (Send kc_caps at start, and check send again.)

yea we could do this at the begining to check. Ill wait on @drashna 's input before I code anything:)

Ouch. :(

If that's the case.... well, there are a number of options.

You can do this:


bool is_mac = false;

void matrix_init_user(void) {
  uint8_t led_state = host_keyboard_leds();
  // Set caps lock LED pin as output
  DDRB |= (1 << 2);
  // Default to off
  PORTB |= (1 << 2);

  register_code(KC_CAPS); //enable/disable CAPSLOCK
  wait_ms(50); // wait, because macs need it to be held, IIRC
  unregister_code(KC_CAPS);
  if (led_state == host_keyboard_leds()) {
    is_mac = true;
    PORTB &= ~(1 << 2); // LED disabled
  }
  register_code(KC_CAPS); //revert the CAPSLOCK state
  wait_ms(50);
  unregister_code(KC_CAPS);
}

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
  static bool is_mac_capslock_enabled; 
  switch (keycode) {
  case KC_CAPS:
    if (record->event.pressed && is_mac) {
      is_mac_capslock_enabled ^= 1; // toggles value
      if (is_mac_capslock_enabled) {
        PORTB |= (1 << 2); // enable LED since caps is enabled
      } else {
        PORTB &= ~(1 << 2); // disable LED since caps is disabled
      }
    }
    break;
  }
  return true;
}

void led_set_user(uint8_t usb_led) {
  if (!is_mac) {
    if (usb_led & (1 << USB_LED_CAPS_LOCK)) {
        PORTB &= ~(1 << 2);
    } else {
        PORTB |= (1 << 2);
    }
  }
}

This is complicated, overly so. But it should work. It will toggle on keypress only if it's a mac, but should work normally on Windows/Linux.

Marked as bug and won't fix, since this is an OS issue, and not a QMK issue. Unfortunately.

@drashna Ty for that code, It work fine under macOs, but don't sync on windows.

Give it another wait before:
if (led_state == host_keyboard_leds()) {

@yiancar Nope, it doesn't fix it.

Could u use the console/ printf to check the value of led_state at the different times?

Maybe it needs some delay before the first time u read it until hid is established

I will do it tomorrow, btw printf print like SEND_STRING? if not how do I see stdout?

If you have a look in docs.qmk.fm there is a section about debug and how printf gets displayed in qmk toolbox under windows

I use xprintf. It prints regardless if debugging is enabled or not.

So something like xprintf("capslock enabled: %b", led_state); should work.

@drashna @yiancar

#include "dz60.h"
#include "led.h"
#include <print.h>
bool is_mac = false;

void matrix_init_user(void) {
  uint8_t led_state = host_keyboard_leds();
  // Set caps lock LED pin as output
  xprintf("Init capslock enabled: %b", led_state);
  DDRB |= (1 << 2);
  // Default to off
  PORTB |= (1 << 2);

  register_code(KC_CAPS); //enable/disable CAPSLOCK
  wait_ms(50); // wait, because macs need it to be held, IIRC
  unregister_code(KC_CAPS);
  wait_ms(50); // wait, because macs need it to be held, IIRC
  xprintf("Before ckeck capslock enabled: %b", host_keyboard_leds() );
  if (led_state == host_keyboard_leds() ){
    is_mac = true;
    PORTB |= (1 << 2); // disable LED
  }
  register_code(KC_CAPS); //revert the CAPSLOCK state
  wait_ms(50);
  unregister_code(KC_CAPS);
  xprintf("Final ckeck capslock enabled: %b", led_state);
}

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
static bool is_mac_capslock_enabled;
  switch (keycode) {
  case KC_CAPS:
  xprintf("internal capslock enabled: %b", is_mac_capslock_enabled); // shuold go after check event pressed.
    if (record->event.pressed && is_mac) {
      is_mac_capslock_enabled ^= 1; // toggles value
      if (is_mac_capslock_enabled) {
        PORTB &= ~(1 << 2); // enable LED since caps is enabled
      } else {
        PORTB |= (1 << 2); // disable LED since caps is disabled
      }
    }
    break;
  }
  return true;
}

void led_set_user(uint8_t usb_led) {
  if (!is_mac) {
    if (usb_led & (1 << USB_LED_CAPS_LOCK)) {
        PORTB &= ~(1 << 2);
    } else {
        PORTB |= (1 << 2);
    }
  }
}

}

"Nothing" show when I plug, but xprintf in process record work. So I think we need add delay until hid is established ...

Also there is a small chance when I plug and the caps led sync out with cap state in macOs.

Yea give a go to the delay. I am on holiday atm so unable to test anything until at least monday

If it needs a delay, then it may be best to move it from the matrix_init_user function, and move it to the matrix_scan_user function, and use a "run guard" so that it only checks once.


void led_set_user(uint8_t usb_led) {
    if (!init){
  wait_ms(500); // tested with 800 freeze the keyboard on windows
  uint8_t led_state = host_keyboard_leds();
  xprintf("Init capslock enabled: %b", led_state);

  register_code(KC_CAPS); //enable/disable CAPSLOCK
  wait_ms(80); // wait, because macs need it to be held, IIRC
  unregister_code(KC_CAPS);
  wait_ms(80); // wait, because macs need it to be held, IIRC
  xprintf("Before ckeck capslock enabled: %b", host_keyboard_leds() );
  if (led_state == host_keyboard_leds() ){
    is_mac = true;
    PORTB |= (1 << 2); // disable LED
  }
  register_code(KC_CAPS); //revert the CAPSLOCK state
  wait_ms(80);
  unregister_code(KC_CAPS);
  xprintf("Final ckeck capslock enabled: %b", led_state);
  init = true;
}

  if (!is_mac) {
    if (usb_led & (1 << USB_LED_CAPS_LOCK)) {
        PORTB &= ~(1 << 2);
    } else {
        PORTB |= (1 << 2);
    }
  }

If I set a high delay like 800 windows stop recognising it(paused at that), but no problem on mac.
And if I set like 500 it work on both, but no sync on windows(same than before).
Any hook we can use to make sure that it's called after usb connected with host?

I fixed it by moving the check to process_record. By default it use non macOS cap LED method, after the first caps key press it check and set state for LED.

Here is the whole dz60.c for anyone who need cap led on mac.

#include "dz60.h"
#include "led.h"
bool is_mac = false;
bool init = false;

void matrix_init_user(void) {
  // Set caps lock LED pin as output
  DDRB |= (1 << 2);
  // Default to off
  PORTB |= (1 << 2);
}

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
static bool is_mac_capslock_enabled;
  switch (keycode) {
  case KC_CAPS:
    if (!init){
        uint8_t led_state = host_keyboard_leds();
        register_code(KC_CAPS); //enable/disable CAPSLOCK
        wait_ms(60); // wait, because macs need it to be held, IIRC
        unregister_code(KC_CAPS);
        wait_ms(60);

  if (led_state == host_keyboard_leds() ){
        is_mac = true;
        PORTB |= (1 << 2); // disable LED
  }

  register_code(KC_CAPS); //revert the CAPSLOCK state
  wait_ms(60);
  unregister_code(KC_CAPS);
  init = true;
    }

    if (record->event.pressed && is_mac) {
        is_mac_capslock_enabled ^= 1; // toggles value
        if (is_mac_capslock_enabled) {
            PORTB &= ~(1 << 2); // enable LED since caps is enabled
        } else {
        PORTB |= (1 << 2); // disable LED since caps is disabled
        }
    }
    break;
  }
  return true;
}

void led_set_user(uint8_t usb_led) {
  if (!is_mac) {
    if (usb_led & (1 << USB_LED_CAPS_LOCK)) {
        PORTB &= ~(1 << 2);
    } else {
        PORTB |= (1 << 2);
    }
  }
}

Also, does process_record process LT(layer,kc) ? because this don't work if I use that key(led don't change but cap lock state do change).

Btw about the default dz60.c, the led_set_user method is wrong(not working in both os), so can I make a pull request to change that to the code in first post?

Interestingly with the YMD75, which is based on the ATmega32a; the caps lock led works in both windows and osx. There is no mac-specific code.
I wonder why this is.
@Menchen maybe it would be best to put together a dz60.c where the capslock led works in both windows and osx and pull request that. Although in my opinion, the fix should be implemented at a higher level and not per-keyboard.

I agree with a pull request that fix for both system,(so it work oob), but it's still too hacky to implement, because process_record don't work with LT macro(I think) and cap key flash on the first press on non mac (maybe we can "fix" this by decreasing delay). Although a hight level patch would fix it without any problem I mentioned. (Using hook right after hid connected, another global level hook when registering capkey).

Also, I think this is a nice things to have as new "features" so we can manage Unicode input more easily.

And in the case that only dz60 is not working, this issue become a qmk issue so I think we should fix this issue as for most people caps led is a must...(personally I remapped yo esd for vim but still annoying using without knowing cap state. )

Also a problem on my Satan, although it and the DZ60 might be too closely related for that to be of any interest. However...

@WarmCatUK, the YMD75's rules.mk says it uses V-USB instead of LUFA (since the 32A is not a native USB device). Perhaps there's something different in the HID descriptor?

I don't think macOS is discriminating based on the VID/PID (as it does for the Fn key) since the caps lock LEDs on regular membrane keyboards and my WASD v2 work fine with Macs.

@fauxpark I am sure that all "regular" keyboards have a fallback mechanism in case caps lock is pressed but no hid report is received. I have sniffed the hid bytes and macOS simply doesnt send any back for the leds no matter the keyboard (even for an apple one).

I am pretty sure this is a qmk issue.

@Menchen would you be kind enough to put the snippet of code in the faq section of the docs?:) I know a lot of people dont use capslock leds in general so it would be good to save this somewhere in the docs for those who care!

macOS simply doesnt send any back for the leds no matter the keyboard (even for an apple one).

It must, because on macOS the caps lock only activates when held for a short time, and I have seen this on every non-QMK keyboard I've tried. If the LED state were solely internal the keyboard would not know to do that.

I used hidapi to test this. You can repeat my experiment if you like

I have looked at the LED usages before using this tool - it's not been updated in a while and needs a lot of work to get it running though (and my fixed up version crashes on Mojave, otherwise I'd attach it here).

I noticed that while I can see keypresses and turn the LEDs on and off regardless of lock state, LED state changes are not reported. I suspect this may be a bug in Apple's HID API.

I dont know if its a bug of reporting, or designed like that.

I have asked in apples dev forum before without answer.
https://forums.developer.apple.com/thread/86470?q=hid%20led*

And ofcource I dont want to pay money to get TSI support access. IF you guys know anyone who is an avid apple dev and can help us out, that would be nice

@yiancar I think we need solve LT macro don't work with process record first before we add to faq....
Also what about pull request to update default led function to make it work under windows/linux? (the dz60.c in this repo right now don't work)

And I think we should trace what commit broke led on macOs.... and fix from there.

A couple of things I found.

  • After comparing the LUFA and V-USB descriptors, and the descriptors of the WASD v2 and a random Dell keyboard, I couldn't find anything that would cause macOS to not send the LED state. So looks like I was wrong there.
  • In fact, Wireshark seems to be telling me that macOS is sending the LED state. Shortly after the report containing the caps lock keydown, it sends a SetReport message to the keyboard that looks very suspicious. The contents of this packet are either 0x02 after turning caps lock on, or 0x00 after turning it off. The caps lock LED usage also just so happens to be 0x02.

Here are the pcap files so you can see for yourself. This generic keyboard spams its keystate whereas QMK stays quiet unless it's got something to say, however I don't believe that's relevant.

capslock.zip

My thinking now is that this might be an issue with LUFA, based on the above claim that V-USB works fine, though I can't verify that myself.

@cjcookson found the smoking gun over at https://github.com/qmk/qmk_firmware/issues/4517#issuecomment-443910435. Since #3951, LUFA now tries to read two bytes from the SetReport request (the report ID and LED state instead of just the latter) when you have NKRO or media keys enabled, even if you haven't explicitly turned on the new shared endpoint functionality.

does reading only the LED state work then?

Yes, and that's also what it's doing, but the ifdef is wrong. The keyboard descriptor only has a report ID (and thus the host will give it back to you) when KEYBOARD_SHARED_EP = yes (and this implies SHARED_EP_ENABLE in case you have extrakey or NKRO off), so by changing it to #ifdef KEYBOARD_SHARED_EP, it works in both cases.

Okay, I've merged the fix provided by @fauxpark, and hopefully updating your local repo should fix the issue!

Was this page helpful?
0 / 5 - 0 ratings