Indicating Success on QMK Keyboards

Posted on June 23, 2024 by Richard Goulter
Tags: ,

In the previous post, I discussed ways of using QMK leader key sequences in QMK keymaps.

One of the downsides with this is that QMK leader key sequences are handled only after a timeout (i.e. enter the key sequence, then wait a moment).

One way of improving the UX of this is having the keyboard provide some feedback when a leader sequence is activated.

The QMK docs page for the Leader key sequences gives an example using the AUDIO feature, with PLAY_SONG, and will play song indicating whether a leader key sequences was activated successfully or not.

My keyboards don’t support the AUDIO feature.

But, I do have keyboards with per-key RGB or with haptic feedback.

The leader_end_user function (which handles leader key sequences after timeout) can be updated with:

void leader_end_user(void) {
    bool did_leader_succeed = true;
    if (leader_sequence_two_keys(KC_Q, KC_B)) { // mnemonic: QMK Boot
        reset_keyboard();
    } else if (/* ... */) {
        /* .... */
    } else {
        did_leader_succeed = false;
    }
    leader_end_notify(did_leader_succeed);
}

i.e. call some leader_end_notify function, unless the leader key sequence wasn’t recognised. (It’s conceptually neater to initialize did_leader_succeed with false, and only set it to true when handling a key sequence.. but, that would take more lines of code for the same effect).

The leader_end_notify can be written like this:

void leader_end_notify(bool succeeded) {
#    ifdef HAPTIC_ENABLE
    if (succeeded && haptic_get_enable()) {
        haptic_play();
    }
#    endif
#    ifdef RGB_MATRIX_ENABLE
    rgb_matrix_blink_start(succeeded);
#    endif
}

The rgb_matrix_blink_start is a function can is a bit trickier.

On a split keyboard, it’s a bit difficult to communicate state between the master half and the slave half. – My first attempt, I tried using the rgb_matrix_set_color_all function; but, this didn’t update the RGB LEDs on the slave half.

I work with this limitation by using the RGB matrix effects. The QMK framework’s split keyboard transport protocol already handles syncing state for RGB effects.

So, we “blink” the keyboard by:

Hence, the implmentation, we have some extra state to store:

#ifdef RGB_MATRIX_ENABLE
uint8_t  old_mode               = 0;
HSV      old_hsv                = {HSV_OFF};
uint32_t rgb_matrix_blink_timer = 0;
bool     blinking_active        = false;
#endif

In rgb_matrix_blink_start, we store the current RGB effect and colour, and keep track of the time:

void rgb_matrix_blink_start(bool succeeded) {
    /* impl. note: in order to get the 'blink' to work across split keyboard,
     *  save the old mode & hsv, then set to solid white.
     * The old mode & hsv are then restored after some time,
     *  in the rgb_matrix_indicators_user callback.
     */
    old_mode = rgb_matrix_get_mode();
    old_hsv  = rgb_matrix_get_hsv();

    rgb_matrix_mode_noeeprom(RGB_MATRIX_SOLID_COLOR);
    if (succeeded) {
        rgb_matrix_sethsv_noeeprom(HSV_WHITE);
    } else {
        rgb_matrix_sethsv_noeeprom(HSV_RED);
    }

    rgb_matrix_blink_timer = timer_read32();
    blinking_active        = true;
}

void rgb_matrix_blink_end(void) {
    rgb_matrix_mode_noeeprom(old_mode);
    rgb_matrix_sethsv_noeeprom(old_hsv.h, old_hsv.s, old_hsv.v);

    blinking_active = false;
}

and in rgb_matrix_blink_end, we restore the RGB effect and colour.

This rgb_matrix_blink_end is called by a “housekeeping task”, which is called by QMK at the end of each scan loop. It simply checks the blink timer, and calls the blink end function if enough time has passed.

void housekeeping_task_user(void) {
    if (blinking_active && timer_elapsed32(rgb_matrix_blink_timer) >= RGB_MATRIX_BLINK_INTERVAL) {
        rgb_matrix_blink_end();
    }
}

With this, I can now confidently be notified that a leader sequence succeeded, using either haptic feedback, or RGB matrix functionality.

I expect that code for the RGBLIGHT QMK feature would be similar to the RGB matrix implementation above.

So, e.g., if I use a leader key sequence to enable caps word, I can be confident caps word has activated. Or if I invoke a sequence and don’t see the keyboard ‘blink’ (or don’t receive haptic feedback), then I can understand that I failed to enter a leader key sequence.


Newer post Older post