Search code examples
qmkqmk-firmware

How can I cause a synthetic autoshift keypress in QMK (for the Moonlander)?


I have written some code for the Moonlander to indicate that the Num Lock is active by changing its LED colour, like so:

bool process_record_user(uint16_t keycode, keyrecord_t *record) {
  switch (keycode) {
    case TOG_NUMS:
      if (record->event.pressed) {
        tap_code(KC_NUM_LOCK); // Synthetic Num Lock key press
        isToggle[IND_NUMS] = !isToggle[IND_NUMS]; // Toggle the indicator
        set_toggle_indicator(IND_NUMS); // Change LED colour based on isToggle value
      }
      return true;
    default:
      return true;
  }
  return true;
}

This works as expected: The custom keycode TOG_NUMS toggles the numeric keypad by calling tap_code and makes a call to my set_toggle_indicator function to update the ledmap. When I try the same thing with my custom autoshift key, however, I run into issues. This is what I did to try and follow the same pattern:

case TOG_ASHF:
  if (record->event.pressed) {
    tap_code(KC_ASTG);
    isToggle[IND_ASHF] = !isToggle[IND_ASHF];
    set_toggle_indicator(IND_ASHF);
  }
  return true;

The above doesn't compile, however, since the type of the KC_ASTG keycode is integer, while the tap_code parameter is an unsigned char. The compiler gives me this error message:

error: unsigned conversion from 'int' to 'uint8_t' {aka 'unsigned char'} changes value from '23578' to '26'

If I replace tap_code with tap_code16, my code compiles, but the key behaves strangely. It appears to simply give me a lowercase w when pressed. How can I get the KC_ASTG key press to trigger without using tapcode?


Solution

  • I figured it out! A synthetic key press was the wrong approach. The KC_ASTG keycode does not have a matching scancode like the numeric keypad key does. Instead of sending a scancode to the OS via USB, the logic is handled in the process_auto_shift middleware. I included process_auto_shift.h into my keymap file and copied the implementation of the middleware into the TOG_ASHF case statement, like so:

    case TOG_ASHF:
      if (record->event.pressed) {
        autoshift_toggle();
        isToggle[IND_ASHF] = !isToggle[IND_ASHF];
        set_toggle_indicator(IND_ASHF);
      }
      return true;
    

    It behaves as expected.