Browse Source

[Keymap] Add vitoni keymap for GMMK Pro (ISO) (#15006)

* [Keymap] Add vitoni layout for GMMK Pro (ISO)

Keymap has layered cursor keys similar to laptop keyboards.

* Configure RGB defaults for startup

* Configure encoder to change value/brightness on FN layer

* Remove FN layer and add dedicated RGB layer

* Make RGB layer sticky (using TG) to avoid holding FN while configuring RGB

* Add RGB indicators for active layers

* Add RGB indicator for active RESET mode

Signed-off-by: Victor Toni <victor.toni@gmail.com>

* Configure idle / USB suspend settings

* Add RGB fade in when resuming after suspend

* Add RGB fade out before suspend

* Add fade out before idle

* Add breathe effect when idle
Victor Toni 3 years ago
parent
commit
6209122213

+ 20 - 0
keyboards/gmmk/pro/iso/keymaps/vitoni/config.h

@@ -0,0 +1,20 @@
+// Copyright 2021 Victor Toni (@vitoni)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#if defined(RGB_MATRIX_ENABLE)
+    #define RGB_MATRIX_STARTUP_MODE RGB_MATRIX_SOLID_COLOR
+     // number of milliseconds to wait until turning off RGB automatically
+    #define RGB_DISABLE_TIMEOUT 300000 // 300 seconds / 5 min
+    // start fading out before getting disabled
+    // fading out is timed (depending on the rgb_matrix_config.speed) to have finished before reaching RGB_DISABLE_TIMEOUT
+    #define RGB_DISABLE_WITH_FADE_OUT
+    #define RGB_DISABLE_WHEN_USB_SUSPENDED
+    // number of milliseconds to wait until activating RGB idle effects
+    #define RGB_IDLE_TIMEOUT 4500 // 4.5 seconds
+    // activate breathe effect when idle
+    #define RGB_IDLE_BREATHE
+    // fade in when we have been suspended
+    #define RGB_FADE_IN
+#endif

+ 148 - 0
keyboards/gmmk/pro/iso/keymaps/vitoni/keymap.c

@@ -0,0 +1,148 @@
+// Copyright 2021 Glorious, LLC <salman@pcgamingrace.com>,
+// Copyright 2021 Victor Toni (@vitoni)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include QMK_KEYBOARD_H
+
+#include "vitoni.h"
+
+enum layer_names {
+    _BASE,
+    _MOV,
+    _RGB
+};
+
+// clang-format off
+const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
+
+//      ESC      F1       F2       F3       F4       F5       F6       F7       F8       F9       F10      F11      F12	     Prt           Rotary(Mute)
+//      ~        1        2        3        4        5        6        7        8        9        0         -       (=)	     BackSpc           Del
+//      Tab      Q        W        E        R        T        Y        U        I        O        P        [        ]                          PgUp
+//      Caps     A        S        D        F        G        H        J        K        L        ;        "        #        Enter             PgDn
+//      Sh_L     /        Z        X        C        V        B        N        M        ,        .        ?                 Sh_R     Up       End
+//      Ct_L     Win_L    Alt_L                               SPACE                               Alt_R    FN       Ct_R     Left     Down     Right
+
+
+    //
+    // This keyboard defaults to 6KRO instead of NKRO for compatibility reasons (some KVMs and BIOSes are incompatible with NKRO).
+    // Since this is, among other things, a "gaming" keyboard, a key combination to enable NKRO on the fly is provided for convenience.
+    // Press CAPS+N to toggle between 6KRO and NKRO. This setting is persisted to the EEPROM and thus persists between restarts.
+    [_BASE] = LAYOUT(
+        KC_ESC,  KC_F1,   KC_F2,   KC_F3,   KC_F4,   KC_F5,   KC_F6,   KC_F7,   KC_F8,   KC_F9,   KC_F10,  KC_F11,  KC_F12,  KC_PSCR,          KC_MUTE,
+        KC_GRV,  KC_1,    KC_2,    KC_3,    KC_4,    KC_5,    KC_6,    KC_7,    KC_8,    KC_9,    KC_0,    KC_MINS, KC_EQL,  KC_BSPC,          KC_DEL,
+        KC_TAB,  KC_Q,    KC_W,    KC_E,    KC_R,    KC_T,    KC_Y,    KC_U,    KC_I,    KC_O,    KC_P,    KC_LBRC, KC_RBRC,                   KC_PGUP,
+        MO(_MOV), KC_A,   KC_S,    KC_D,    KC_F,    KC_G,    KC_H,    KC_J,    KC_K,    KC_L,    KC_SCLN, KC_QUOT, KC_NUHS, KC_ENT,           KC_PGDN,
+        KC_LSFT, KC_NUBS, KC_Z,    KC_X,    KC_C,    KC_V,    KC_B,    KC_N,    KC_M,    KC_COMM, KC_DOT,  KC_SLSH,          KC_RSFT, KC_UP,   KC_END,
+        KC_LCTL, KC_LGUI, KC_LALT,                            KC_SPC,                             KC_RALT, TG(_RGB),KC_RCTL, KC_LEFT, KC_DOWN, KC_RGHT
+    ),
+
+    [_MOV] = LAYOUT(
+        RESET,   KC_MYCM, KC_WHOM, KC_CALC, KC_MSEL, KC_MPRV, KC_MPLY, KC_MSTP, KC_MNXT, KC_MUTE, KC_VOLD, KC_VOLU, _______, _______,          _______,
+        _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,          KC_INS,
+        _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,                   _______,
+        _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,          _______,
+        _______, _______, _______, _______, _______, _______, _______, NK_TOGG, _______, _______, _______, _______,          _______, KC_PGUP, _______,
+        _______, _______, _______,                            _______,                            _______, _______, _______, KC_HOME, KC_PGDN, KC_END
+    ),
+
+    [_RGB] = LAYOUT(
+        _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,          _______,
+        _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, RESET,            RGB_MOD,
+        _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,                   RGB_RMOD,
+        _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, RGB_TOG,          RGB_SPI,
+        _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,          _______, RGB_SAI, RGB_SPD,
+        _______, _______, _______,                            _______,                            _______, _______, _______, RGB_HUD, RGB_SAD, RGB_HUI
+    ),
+
+};
+// clang-format on
+
+#if defined(ENCODER_ENABLE)
+bool encoder_update_user(uint8_t index, bool clockwise) {
+    switch (get_highest_layer(layer_state)) {
+        case _MOV:
+            if (clockwise) {
+                tap_code16(C(KC_TAB));
+            } else {
+                tap_code16(S(C(KC_TAB)));
+            }
+            break;
+#if defined(RGB_MATRIX_ENABLE)
+        case _RGB:
+            if (clockwise) {
+                rgb_matrix_increase_val_noeeprom();
+            } else {
+                rgb_matrix_decrease_val_noeeprom();
+            }
+            break;
+#endif // RGB_MATRIX_ENABLE
+        default:
+            if (clockwise) {
+                tap_code(KC_VOLU);
+            } else {
+                tap_code(KC_VOLD);
+            }
+            break;
+    }
+    return true;
+}
+#endif // ENCODER_ENABLE
+
+#if defined(RGB_MATRIX_ENABLE)
+/*
+* Set up default RGB color.
+*/
+void rgb_matrix_set_default_color(void) {
+    rgb_matrix_sethsv_noeeprom_user(HSV_CHARTREUSE);
+}
+
+/*
+* Set up RGB defaults.
+*/
+void rgb_matrix_configure_default_settings(void) {
+    rgb_matrix_set_default_color();
+}
+
+void keyboard_post_init_user(void) {
+    rgb_matrix_enable_noeeprom();
+    rgb_matrix_configure_default_settings();
+}
+
+/*
+* Use RGB underglow to indicate specific layers.
+*/
+layer_state_t layer_state_set_user(layer_state_t state) {
+    switch (get_highest_layer(state)) {
+        case _MOV:
+            rgb_matrix_sethsv_noeeprom_user(HSV_SPRINGGREEN);
+            break;
+        case _RGB:
+            rgb_matrix_sethsv_noeeprom_user(HSV_GREEN);
+            break;
+        default: // for any other layer
+            rgb_matrix_set_default_color();
+            break;
+    }
+    return state;
+}
+
+void matrix_scan_user(void) {
+    matrix_scan_user_rgb();
+}
+
+bool process_record_user(uint16_t keycode, keyrecord_t *record) {
+    if (!process_record_user_rgb(keycode, record)) {
+        return false;
+    }
+
+    switch (keycode) {
+        case RESET:  // when activating RESET mode for flashing
+            if (record->event.pressed) {
+                rgb_matrix_set_color_all(63, 0, 0);
+                rgb_matrix_driver.flush();
+            }
+            return true;
+    }
+    return true; // Process all other keycodes normally
+}
+#endif // RGB_MATRIX_ENABLE

+ 104 - 0
keyboards/gmmk/pro/iso/keymaps/vitoni/readme.adoc

@@ -0,0 +1,104 @@
+= ViToni's keymap for GMMK Pro ISO
+
+== Layout
+Based on the stock layout but making use of CAPS as FN similar to laptop keyboards.
+This frees up the left row for other uses (although not remapped yet).
+Since both Delete and Insert are used for coding they are part of the CAPS layer as well.
+
+The differences are as follows:
+
+=== Layer 0 (`_BASE`)
+Mostly stock + CAPS goes to layer `_MOV`.
+FN toggles the layer `_RGB`.
+
+=== Layer 1 (`_MOV`), accessed by pressing `CAPS` on layer `_BASE`
+[%header]
+|===
+| Key / Action          | Mapped to
+| ESC                   | _RESET_
+| F1                    | KC_MYCM
+| F2                    | KC_WHOM
+| F3                    | KC_CALC
+| F4                    | KC_MSEL
+| F5                    | KC_MPRV
+| F6                    | KC_MPLY
+| F7                    | KC_MSTP
+| F8                    | KC_MNXT
+| F9                    | KC_MUTE
+| F10                   | KC_VOLD
+| F11                   | KC_VOLU
+| N                     | NK_TOGG
+| Delete                | Insert
+| Left                  | Home
+| Right                 | End
+| Up                    | PgUp
+| Down                  | PgDn
+|===
+
+=== Layer 2 (`_RGB`), accessed by pressing `FN` on layer `_BASE`
+Revamped the stock FN layer to focus on RGB only.
+
+[%header]
+|===
+| Key / Action          | Mapped to
+| Knob clockwise        | Value/Brightness up
+| Knob anti-clockwise   | Value/Brightness down
+| Backspace             | _RESET_
+| Enter                 | RGB_TOG
+| Del                   | RGB_MOD
+| PgUp                  | RGB_RMOD
+| PgDn                  | RGB_SPI
+| End                   | RGB_SPD
+| Left                  | RGB_HUD
+| Right                 | RGB_HUI
+| Up                    | RGB_SAI
+| Down                  | RGB_SAD
+|===
+
+No other changes have been made.
+
+== RGB light
+
+The code customizing RGB light usage is decribed here:
+
+* link:../../../../../../users/vitoni/readme.adoc[/users/vitoni/readme.adoc]
+
+When using  `RGB_DISABLE_TIMEOUT` addtional options are available:
+
+* `RGB_FADE_IN` makes the RGB lights fade in instead of setting the value/brightness to 100% (implicitly due to HSV including the brightness) when resuming after RGB lights have been turned off.
+Fade in occurs when the keyboard is initialized and when the RGB brightness has been changed (e.g. suspending, fade out, etc.).
+* `RGB_DISABLE_WITH_FADE_OUT` activates fade out before the keyboard is disabled by `RGB_DISABLE_TIMEOUT`.
+
+Parameters used to define the behavior are:
+[%header]
+|===
+|Key | Default | Description
+
+| RGB_MATRIX_MAXIMUM_BRIGHTNESS
+| 200 (&lt;= UNIT8_MAX)
+| Maximum assumed value for brightness.
+Used to calculate lead time for fade out before suspend timeout.
+
+|===
+
+`RGB_IDLE_TIMEOUT` enables fading out after being idle for the defined time and allows
+* `RGB_IDLE_BREATHE` also activates a brethe effect while idling.
+
+[%header]
+|===
+|Key | Default | Description
+
+|RGB_IDLE_TIMEOUT
+|4500
+|Time in milliseconds without activity before considered to be idle.
+
+|RGB_IDLE_MINIMUM_BRIGHTNESS
+|`RGB_MATRIX_MAXIMUM_BRIGHTNESS` / 5
+|Brightness value RGB is dimmed to when starting to idle. +
+When breathing used as the lower bound of the brightness value.
+
+|RGB_IDLE_MAXIMUM_BRIGHTNESS
+|`RGB_MATRIX_MAXIMUM_BRIGHTNESS` * 2/5
+|Upper bound of brightness value of the RGB light while breathing.
+
+|===

+ 16 - 0
users/vitoni/readme.adoc

@@ -0,0 +1,16 @@
+= User functions
+
+Functions are mostly related to changing the RGB lights depending on user interaction and when idling.
+
+== utils.h
+
+Common functions are declared in link:utils.h[]. These function are not directly RGB related but used to modify state and calculate values.
+
+== rgb_matrix_effects.h
+
+Functions in link:rgb_matrix_effects.h[] make use of common function in `utils.h` and are used to create to RGB matrix effects such as fading or breathing.
+
+== vitoni.h
+
+The functions declared in link:vitoni.h[] are used as entry points for usage of RGB effects.
+One entry point is `matrix_scan` based for regular task while the other is `process_record` based for user activity tasks.

+ 236 - 0
users/vitoni/rgb_matrix_effects.c

@@ -0,0 +1,236 @@
+// Copyright 2021 Victor Toni (@vitoni)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "rgb_matrix_effects.h"
+
+#include <rgb_matrix.h>
+#include <lib/lib8tion/lib8tion.h>
+
+#include "utils.h"
+
+/*
+ Offset used to start at the right point in th curve to avoid big jumps in brightness
+  0 =>    0% (signed) =>  50% (unsigned)
+  64 =>  100% (signed) => 100% (unsigned)
+ 128 =>    0% (signed) =>  50% (unsigned)
+ 192 => -100% (signed) =>   0% (unsigned)
+*/
+enum PHASE {
+    PHASE_ZERO_RAISING
+    ,PHASE_HIGH
+    ,PHASE_ZERO_FALLING
+    ,PHASE_LOW
+};
+
+/**
+ * @brief Calculates the offset so that a specific time is aligned to a specific point in the sine curve.
+ * @param[in]   time    The time for which the offset shopuld be calculated.
+ * @param[in]   phase   Phase which should be reached with the offset
+ * @see PHASE
+ */
+uint8_t offset_for_time(const uint8_t time, const uint8_t phase) {
+    switch (phase) {
+        case PHASE_ZERO_RAISING:
+            return 0 - time;
+        case PHASE_HIGH:
+            return 64 - time;
+        case PHASE_ZERO_FALLING:
+            return 128 - time;
+        case PHASE_LOW:
+            return 192 - time;
+        default:
+            return 0;
+    }
+}
+
+/**
+ * @brief Scales down `g_rgb_timer` so that it can be used for RGB effects.
+ * @return scaled down timer
+ * @see rgb_time_2_scale_w_factor()
+ */
+uint8_t rgb_time_2_scale(void) {
+    static const uint8_t factor = 1;
+    return rgb_time_2_scale_w_factor(factor);
+}
+
+/*
+ * Used to slow down RGB speed.
+ */
+static const uint8_t rgb_speed_divisor = 8;
+
+/**
+ * @brief Scales down `g_rgb_timer` so that it can be used for RGB effects.
+ * @details Usually these calculations aredone internally by some RGB effects.
+ This method exposed to scaling so that all effects to have same timebase. If `rgb_matrix_config.speed` all effects are affected the same.
+ * @param[in]   factor The factor can be used to speed up some operations in relation to others.
+ * @return scaled down timer taking into account the given factor
+ * @see g_rgb_timer
+ * @see rgb_matrix_config.speed
+ */
+uint8_t rgb_time_2_scale_w_factor(const uint8_t rgb_speed_factor) {
+    const uint8_t scaled_time = scale16by8(g_rgb_timer, rgb_matrix_config.speed * rgb_speed_factor / rgb_speed_divisor);
+
+    return scaled_time;
+}
+
+/**
+ * @brief Inverse function to calculate time required to execute `timer` steps.
+ * @details This method allows calculation of the time needed to execute N `timer`steps.
+ Usefull when using a scaled down time but requiring the time needed to perform these steps.
+ * @param[in]   scaled_time scaled down timer to inverse to time
+ * @return time corresponding to scaled down time
+ * @see rgb_time_2_scale()
+ */
+uint16_t scale_2_rgb_time(const uint8_t scaled_time) {
+    const uint16_t time = scaled_time * rgb_speed_divisor * UINT8_MAX / rgb_matrix_config.speed;
+
+    return time;
+}
+
+bool fade_in_ranged(const uint8_t time, const uint8_t range_min, const uint8_t range_max) {
+    static const uint8_t max_delta = 1;
+    return scaled_sin_up(time, range_min, range_max, max_delta, &(rgb_matrix_config.hsv.v));
+}
+
+bool fade_out_ranged(const uint8_t time, const uint8_t range_min, const uint8_t range_max) {
+    static const uint8_t max_delta = 1;
+    return scaled_sin_down(time, range_min, range_max, max_delta, &(rgb_matrix_config.hsv.v));
+}
+
+/**
+ * @brief Convenience method to eventually skip the value part when setting HSV.
+ * @details When setting HSV this includes the value/brightness.
+ As changing brightness might interfer with fading or breathing effects,
+ this method can skip the value part of HSV (depending on the preprocessor flag: RGB_FADE_IN).
+ * @param[in]   hue Hue
+ * @param[in]   sat Saturation
+ * @param[in]   hue Value (brightness)
+ * @see rgb_matrix_sethsv_noeeprom()
+ */
+void rgb_matrix_sethsv_noeeprom_user(const uint16_t hue, const uint8_t sat, const uint8_t val) {
+#if defined(RGB_FADE_IN) || defined(RGB_IDLE_TIMEOUT)
+    rgb_matrix_config.hsv.h = hue;
+    rgb_matrix_config.hsv.s = sat;
+    // omitting setting the value to avoid interfering with effects
+//    rgb_matrix_config.hsv.v = val;
+#else
+    rgb_matrix_sethsv_noeeprom(hue, sat, val);
+#endif
+}
+
+#if defined(RGB_FADE_IN) || defined(RGB_IDLE_TIMEOUT)
+/**
+ * @brief Calculates the time offset required by fade in.
+ * @details Using an arbitrary timer any point on the sine curve might be pointed to.
+ * The offest is calculated so that
+ * a) the point is at the lowest point in the curve and the curve is raising
+ * b) the point is near the current brightness (eg. fade in might be called while fading out and the lowest value has not yet been reached).
+ * @param[in]   time Current time usually represented by (usually scaled) timer
+ * @return Offset required so that time matches the current brightness
+ */
+uint8_t calc_fade_in_offset(const uint8_t time) {
+    static const uint8_t max_steps = UINT8_MAX/2;
+    static const uint8_t range_min = 0;
+    static const uint8_t range_max = RGB_MATRIX_MAXIMUM_BRIGHTNESS;
+
+    // start at the right point in the sine curve
+    uint8_t time_offset = offset_for_time(time, PHASE_LOW);
+
+    // find the right offset to match the current brightness
+    for (int i = 1; i < max_steps; i++) {
+        const uint8_t value = scaled_sin(time + time_offset + 1, range_min, range_max);
+        if (in_range(value, range_min, range_max) && value < rgb_matrix_config.hsv.v) {
+            time_offset++;
+        } else {
+            break;
+        }
+    }
+
+    return time_offset;
+}
+
+/**
+ * @brief Increases value/brightness until reaching RGB_MATRIX_MAXIMUM_BRIGHTNESS based on given timer.
+ * @param[in]   time A (usually scaled) timer
+ * @return Returns `true` if RGB_MATRIX_MAXIMUM_BRIGHTNESS has been reached, `false` otherwise.
+ */
+bool fade_in(const uint8_t time) {
+    static const uint8_t range_min = 0;
+    static const uint8_t range_max = RGB_MATRIX_MAXIMUM_BRIGHTNESS;
+
+    return fade_in_ranged(time, range_min, range_max);
+}
+#endif
+
+#if defined(RGB_DISABLE_WITH_FADE_OUT) || defined(RGB_IDLE_TIMEOUT)
+/**
+ * @brief Calculates the time offset required by fade out.
+ * @details Using an arbitrary timer any point on the Sinus curve might be pointed to.
+ * The offest is calculated so that
+ * a) the point is at the highest point in the curve and the curve is failing
+ * b) the point is near the current brightness (eg. fade out might be called while on breath effect).
+ * @param[in]   time Current time usually represented by a(usually scaled) timer
+ * @return Offset required so that time matches the current brightness
+ */
+uint8_t calc_fade_out_offset(const uint8_t time) {
+    static const uint8_t range_min = 0;
+    static const uint8_t range_max = RGB_MATRIX_MAXIMUM_BRIGHTNESS;
+
+    // start at the right point in the sin() curve
+    uint8_t time_offset = offset_for_time(time, PHASE_HIGH);
+
+    // find the right offset to match the current brightness
+    for (int i = 1; i < 127; i++) {
+        const uint8_t value = scaled_sin(time + time_offset + 1, range_min, range_max);
+        if (in_range(value, range_min, range_max) && rgb_matrix_config.hsv.v < value) {
+            time_offset++;
+        } else {
+            break;
+        }
+    }
+
+    return time_offset;
+}
+#endif
+
+#if defined(RGB_DISABLE_WITH_FADE_OUT)
+/**
+ * @brief Decreases value/brightness until reaching 0 based on given timer.
+ * @param[in]   time A (usually scaled) timer
+ * @return Returns `true` if 0 has been reached, `false` otherwise.
+ */
+bool fade_out(const uint8_t time) {
+    static const uint8_t range_min = 0;
+    static const uint8_t range_max = RGB_MATRIX_MAXIMUM_BRIGHTNESS;
+
+    return fade_out_ranged(time, range_min, range_max);
+}
+#endif
+
+#if defined(RGB_IDLE_TIMEOUT)
+/**
+ * @brief Decreases value/brightness until reaching `RGB_IDLE_MINIMUM_BRIGHTNESS` based on given timer.
+ * @param[in]   time A (usually scaled) timer
+ * @return Returns `true` if `RGB_IDLE_MINIMUM_BRIGHTNESS` has been reached, `false` otherwise.
+ */
+bool idle_fade_out(const uint8_t time) {
+    static const uint8_t range_min = RGB_IDLE_MINIMUM_BRIGHTNESS;
+    static const uint8_t range_max = RGB_MATRIX_MAXIMUM_BRIGHTNESS;
+
+    return fade_out_ranged(time, range_min, range_max);
+}
+
+#if defined(RGB_IDLE_BREATHE)
+/**
+ * @brief Changes value/brightness to create a breathing effect based on given timer.
+ * @details Brightness will breathe in the range starting from `RGB_IDLE_MINIMUM_BRIGHTNESS` to `RGB_IDLE_MAXIMUM_BRIGHTNESS`.
+ * @param[in]   time A (usually scaled) timer
+ */
+void idle_breathe(const uint8_t time) {
+    static const uint8_t range_min = RGB_IDLE_MINIMUM_BRIGHTNESS;
+    static const uint8_t range_max = RGB_IDLE_MAXIMUM_BRIGHTNESS;
+
+    rgb_matrix_config.hsv.v = scaled_sin(time, range_min, range_max);
+}
+#endif // RGB_IDLE_BREATHE
+#endif // RGB_IDLE_TIMEOUT

+ 174 - 0
users/vitoni/rgb_matrix_effects.h

@@ -0,0 +1,174 @@
+// Copyright 2021 Victor Toni (@vitoni)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/**
+ * States reflecting the state of the keyboard.
+ * Dependeing on these states various effects can set for the RGB matrix.
+ */
+enum states {
+    REGULAR             //!< when in regular use
+#if defined(RGB_IDLE_TIMEOUT)
+    ,IDLE_FADE_OUT      //!< when started idling
+    ,IDLE               //!< when idling
+#endif
+#if defined(RGB_FADE_IN) || defined(RGB_IDLE_TIMEOUT)
+    ,FADE_IN            //!< when starting initially or before going back to REGULAR
+#endif
+#if defined(RGB_DISABLE_WITH_FADE_OUT)
+    ,FADE_OUT           //!< before supending
+#endif
+    ,SUSPENDED          //!< expecting to be suspended by RGB_DISABLE_TIMEOUT any time
+};
+
+/**
+ * @brief Scales down `g_rgb_timer` so that it can be used for RGB effects.
+ * @details Usually these calculations aredone internally by some RGB effects.
+ This method exposed to scaling so that all effects to have same timebase. If `rgb_matrix_config.speed` all effects are affected the same.
+ * @param[in]   factor The factor can be used to speed up some operations in relation to others.
+ * @return scaled down timer taking into account the given factor
+ * @see g_rgb_timer
+ * @see rgb_matrix_config.speed
+ */
+uint8_t rgb_time_2_scale_w_factor(const uint8_t factor);
+
+/**
+ * @brief Scales down `g_rgb_timer` so that it can be used for RGB effects.
+ * @return scaled down timer
+ * @see rgb_time_2_scale_w_factor()
+ */
+uint8_t rgb_time_2_scale(void);
+
+/**
+ * @brief Inverse function to calculate time required to execute `timer` steps.
+ * @details This method allows calculation of the time needed to execute N `timer`steps.
+ Usefull when using a scaled down time but requiring the time needed to perform these steps.
+ * @param[in]   scaled_time scaled down timer to inverse to time
+ * @return time corresponding to scaled down time
+ * @see rgb_time_2_scale()
+ */
+uint16_t scale_2_rgb_time(const uint8_t scaled_time);
+
+/**
+ * @brief Convenience method to eventually skip the value part when setting HSV.
+ * @details When setting HSV this includes the value/brightness.
+ As changing brightness might interfer with fading or breathing effects,
+ this method can skip the value part of HSV (depending on the preprocessor flag: RGB_FADE_IN).
+ * @param[in]   hue Hue
+ * @param[in]   sat Saturation
+ * @param[in]   hue Value (brightness)
+ * @see rgb_matrix_sethsv_noeeprom()
+ */
+void rgb_matrix_sethsv_noeeprom_user(const uint16_t hue, const uint8_t sat, const uint8_t val);
+
+#if defined(RGB_FADE_IN) || defined(RGB_DISABLE_WITH_FADE_OUT) || defined(RGB_IDLE_TIMEOUT)
+#   if defined(RGB_MATRIX_MAXIMUM_BRIGHTNESS)
+#       if (RGB_MATRIX_MAXIMUM_BRIGHTNESS) < 1
+#           error "RGB_MATRIX_MAXIMUM_BRIGHTNESS must not be less than ONE"
+#       endif
+#       if UINT8_MAX < (RGB_MATRIX_MAXIMUM_BRIGHTNESS)
+#           error "RGB_MATRIX_MAXIMUM_BRIGHTNESS must not be larger than UINT8_MAX"
+#       endif
+#   else
+#       define RGB_MATRIX_MAXIMUM_BRIGHTNESS 200
+#   endif
+#endif
+
+#if defined(RGB_FADE_IN) || defined(RGB_IDLE_TIMEOUT)
+/**
+ * @brief Calculates the time offset required by fade in.
+ * @details Using an arbitrary timer any point on the sine curve might be pointed to.
+ * The offset is calculated so that
+ * a) the point is at the lowest point in the curve and the curve is raising
+ * b) the point is near the current brightness (eg. fade in might be called while fading out and the lowest value has not yet been reached).
+ * @param[in]   time Current time usually represented by a(usually scaled) timer
+ * @return Offset required so that time matches the current brightness
+ */
+uint8_t calc_fade_in_offset(const uint8_t time);
+
+/**
+ * @brief Increases value/brightness until reaching RGB_MATRIX_MAXIMUM_BRIGHTNESS based on given timer.
+ * @param[in]   time A (usually scaled) timer
+ * @return Returns `true` if RGB_MATRIX_MAXIMUM_BRIGHTNESS has been reached, `false` otherwise.
+ */
+bool fade_in(const uint8_t time);
+#endif
+
+#if defined(RGB_DISABLE_WITH_FADE_OUT)
+#   if !defined(RGB_DISABLE_TIMEOUT)
+#       warning "RGB_DISABLE_WITH_FADE_OUT expects RGB_DISABLE_TIMEOUT to be defined"
+#   endif
+#endif
+
+#if defined(RGB_DISABLE_WITH_FADE_OUT) || defined(RGB_IDLE_TIMEOUT)
+/**
+ * @brief Calculates the time offset required by fade out.
+ * @details Using an arbitrary timer any point on the Sinus curve might be pointed to.
+ * The offest is calculated so that
+ * a) the point is at the highest point in the curve and the curve is failing
+ * b) the point is near the current brightness (eg. fade out might be called while on breath effect).
+ * @param[in]   time Current time usually represented by a(usually scaled) timer
+ * @return Offset required so that time matches the current brightness
+ */
+uint8_t calc_fade_out_offset(const uint8_t time);
+#endif
+
+#if defined(RGB_DISABLE_WITH_FADE_OUT)
+/**
+ * @brief Decreases value/brightness until reaching 0 based on given timer.
+ * @param[in]   time A (usually scaled) timer
+ * @return Returns `true` if 0 has been reached, `false` otherwise.
+ */
+bool fade_out(const uint8_t time);
+#endif
+
+#if defined(RGB_IDLE_TIMEOUT)
+#   if RGB_IDLE_TIMEOUT < 0
+#       error "RGB_IDLE_TIMEOUT must not be less than ZERO"
+#   endif
+#   if !defined(RGB_IDLE_MINIMUM_BRIGHTNESS)
+        // minimum brightness when idling
+#       define RGB_IDLE_MINIMUM_BRIGHTNESS (RGB_MATRIX_MAXIMUM_BRIGHTNESS/5)
+#   endif
+#   if RGB_IDLE_MINIMUM_BRIGHTNESS < 0
+#       error "RGB_IDLE_MINIMUM_BRIGHTNESS must not be less than ZERO"
+#   endif // RGB_IDLE_MINIMUM_BRIGHTNESS < 0
+#   if RGB_MATRIX_MAXIMUM_BRIGHTNESS <= RGB_IDLE_MINIMUM_BRIGHTNESS
+#       error "RGB_IDLE_MINIMUM_BRIGHTNESS must be less than RGB_MATRIX_MAXIMUM_BRIGHTNESS"
+#   endif // RGB_MATRIX_MAXIMUM_BRIGHTNESS <= RGB_IDLE_MINIMUM_BRIGHTNESS
+
+/**
+ * @brief Decreases value/brightness until reaching `RGB_IDLE_MINIMUM_BRIGHTNESS` based on given timer.
+ * @param[in]   time A (usually scaled) timer
+ * @return Returns `true` if `RGB_IDLE_MINIMUM_BRIGHTNESS` has been reached, `false` otherwise.
+ */
+bool idle_fade_out(const uint8_t time);
+
+#if defined(RGB_IDLE_BREATHE)
+#   if !defined(RGB_IDLE_MAXIMUM_BRIGHTNESS)
+        // maximum brightness when idling
+#       define RGB_IDLE_MAXIMUM_BRIGHTNESS (RGB_MATRIX_MAXIMUM_BRIGHTNESS*3/5)
+#   endif
+#   if !(0 <= RGB_IDLE_MAXIMUM_BRIGHTNESS)
+#       error "RGB_IDLE_MINIMUM_BRIGHTNESS must not be less than ZERO, was: " RGB_IDLE_MAXIMUM_BRIGHTNESS
+#   endif // RGB_IDLE_MAXIMUM_BRIGHTNESS < 0
+#   if !(RGB_IDLE_MINIMUM_BRIGHTNESS < RGB_IDLE_MAXIMUM_BRIGHTNESS)
+#       error "RGB_IDLE_MINIMUM_BRIGHTNESS must be less than RGB_IDLE_MAXIMUM_BRIGHTNESS"
+#   endif // RGB_IDLE_MAXIMUM_BRIGHTNESS <= RGB_IDLE_MINIMUM_BRIGHTNESS
+#   if !(RGB_IDLE_MAXIMUM_BRIGHTNESS <= RGB_MATRIX_MAXIMUM_BRIGHTNESS)
+#       error "RGB_IDLE_MAXIMUM_BRIGHTNESS must be less than or equal to RGB_MATRIX_MAXIMUM_BRIGHTNESS"
+#   endif // RGB_MATRIX_MAXIMUM_BRIGHTNESS <= RGB_IDLE_MAXIMUM_BRIGHTNESS
+
+/**
+ * @brief Changes value/brightness to create a breathing effect based on given timer.
+ * @details Brightness will breathe in the range starting from `RGB_IDLE_MINIMUM_BRIGHTNESS` to `RGB_IDLE_MAXIMUM_BRIGHTNESS`.
+ * @param[in]   time A (usually scaled) timer
+ */
+void idle_breathe(const uint8_t time);
+#endif // RGB_IDLE_BREATHE
+
+#endif // RGB_IDLE_TIMEOUT

+ 4 - 0
users/vitoni/rules.mk

@@ -0,0 +1,4 @@
+SRC += \
+	vitoni.c \
+	utils.c \
+	rgb_matrix_effects.c

+ 129 - 0
users/vitoni/utils.c

@@ -0,0 +1,129 @@
+// Copyright 2021 Victor Toni (@vitoni)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "utils.h"
+
+#include <lib/lib8tion/lib8tion.h>
+
+/**
+* @brief Changes `*value` to `new_value`.
+* @param[in,out] value      Pointer to variable to be changed.
+* @param[in]     new_value  Value to be changed.
+* @param[in,out] changed    Flag indicating `*value` and `new_value` were different.
+*/
+void update_value(uint8_t *value, const uint8_t new_value, bool *changed) {
+    if (new_value != (*value)) {
+        (*changed) = true;
+        (*value) = new_value;
+    }
+}
+
+/**
+* @brief Checks whether a value is in the given range.
+* @param[in] value      Value to be checked.
+* @param[in] range_min  Lower bound of range (inclusive).
+* @param[in] range_max  Upper bound of range (inclusive).
+* @return `true` if (range_min <= value <= range_max), `false` otherwise
+*/
+bool in_range(const uint8_t value, const uint8_t range_min, const uint8_t range_max) {
+    return range_min <= value && value <= range_max;
+}
+
+/**
+* @brief Calculates the sine value based on sin8() and scales it to the given range (unsigned).
+*
+* Table of values for unscaled sin8() eg. a theta of 0 results to 128 and a theta of 255 (240+15) results to 125.
+        0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
+    +----------------------------------------------------------------
+   0: 128 131 134 137 140 143 146 149 152 155 158 161 164 167 170 173
+  16: 177 179 182 184 187 189 192 194 197 200 202 205 207 210 212 215
+  32: 218 219 221 223 224 226 228 229 231 233 234 236 238 239 241 243
+  48: 245 245 246 246 247 248 248 249 250 250 251 251 252 253 253 254
+  64: 255 254 253 253 252 251 251 250 250 249 248 248 247 246 246 245
+  80: 245 243 241 239 238 236 234 233 231 229 228 226 224 223 221 219
+  96: 218 215 212 210 207 205 202 200 197 194 192 189 187 184 182 179
+ 112: 177 173 170 167 164 161 158 155 152 149 146 143 140 137 134 131
+ 128: 128 125 122 119 116 113 110 107 104 101  98  95  92  89  86  83
+ 144:  79  77  74  72  69  67  64  62  59  56  54  51  49  46  44  41
+ 160:  38  37  35  33  32  30  28  27  25  23  22  20  18  17  15  13
+ 176:  11  11  10  10   9   8   8   7   6   6   5   5   4   3   3   2
+ 192:   1   2   3   3   4   5   5   6   6   7   8   8   9  10  10  11
+ 208:  11  13  15  17  18  20  22  23  25  27  28  30  32  33  35  37
+ 224:  38  41  44  46  49  51  54  56  59  62  64  67  69  72  74  77
+ 240:  79  83  86  89  92  95  98 101 104 107 110 113 116 119 122 125
+*
+* @param[in] theta      Angle (a full circle mapped to 0-255) used as input for sine calculation.
+* @param[in] range_min  Lower bound of range (inclusive).
+* @param[in] range_max  Upper bound of range (inclusive).
+* @return Calculated sine value mapped to the given range.
+*/
+uint8_t scaled_sin(const uint8_t theta, const uint8_t range_min, const uint8_t range_max) {
+    const uint8_t range = range_max - range_min;
+    return scale8(sin8(theta), range) + range_min;
+}
+
+/**
+* @brief Increases the given value until reaching range_max.
+* The increments occur following an upwards sine wave (scaled from range_min to range_max).
+* @param[in]        theta       Angle (a full circle mapped to 0-255) used as input for sine calculation.
+* @param[in]        range_min   Lower bound of range (inclusive).
+* @param[in]        range_max   Upper bound of range (inclusive).
+* @param[in]        max_delta   Maximum delta between value and range_max (due to values being integers and eventually not fully matching).
+* @param[in,out]    value       Reference of variable to be increased
+* @return `true` if value and range_max are within a delta of 3 (chosen by fair dice rolling), `false` otherwise
+* @see scaled_sin()
+*/
+bool scaled_sin_up(const uint8_t theta, const uint8_t range_min, const uint8_t range_max, const uint8_t max_delta, uint8_t *value) {
+    // ensure upper range bound
+    if (range_max <= (*value)) {
+        (*value) = range_max;
+        return true;
+    }
+
+    const uint8_t new_value = scaled_sin(theta, range_min, range_max);
+    if (in_range(new_value, range_min, range_max) && (*value) < new_value) {
+        (*value) = new_value;
+
+        return range_max == (*value);
+    }
+
+    const uint8_t delta = range_max - (*value);
+    if (delta <= max_delta) {
+        (*value) = range_max;
+    }
+
+    return delta <= max_delta;
+}
+
+/**
+* @brief Decreases the given value until reaching range_min.
+* The decrements occur following an downwards sinus wave (scaled from range_min to range_max).
+* @param[in]        theta       Angle (a full circle mapped to 0-255) used as input for sinus calculation.
+* @param[in]        range_min   Lower bound of range (inclusive).
+* @param[in]        range_max   Upper bound of range (inclusive).
+* @param[in]        max_delta   Maximum delta between value and range_min (due to values being integers and eventually not fully matching).
+* @param[in,out]    value       Reference of variable to be decreased
+* @return `true` if value and range_max are within a delta of 3 (chosen by fair dice rolling), `false` otherwise
+* @see scaled_sin()
+*/
+bool scaled_sin_down(const uint8_t theta, const uint8_t range_min, const uint8_t range_max, const uint8_t max_delta, uint8_t *value) {
+    // ensure lower range bound
+    if ((*value) <= range_min) {
+        (*value) = range_min;
+        return true;
+    }
+
+    const uint8_t new_value = scaled_sin(theta, range_min, range_max);
+    if (in_range(new_value, range_min, range_max) && new_value < (*value)) {
+        (*value) = new_value;
+
+        return range_min == (*value);
+    }
+
+    const uint8_t delta = (*value) - range_min;
+    if (delta <= max_delta) {
+        (*value) = range_min;
+    }
+
+    return delta <= max_delta;
+}

+ 80 - 0
users/vitoni/utils.h

@@ -0,0 +1,80 @@
+// Copyright 2021 Victor Toni (@vitoni)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/**
+* @brief Changes `*value` to `new_value`.
+* @param[in,out] value      Pointer to variable to be changed.
+* @param[in]     new_value  Value to be changed.
+* @param[in,out] changed    Flag indicating `*value` and `new_value` were different.
+*/
+void update_value(uint8_t *value, const uint8_t new_value, bool *changed);
+
+/**
+* @brief Checks whether a value is in the given range.
+* @param[in] value      Value to be checked.
+* @param[in] range_min  Lower bound of range (inclusive).
+* @param[in] range_max  Upper bound of range (inclusive).
+* @return `true` if (range_min <= value <= range_max), `false` otherwise
+*/
+bool in_range(const uint8_t value, const uint8_t range_min, const uint8_t range_max);
+
+/**
+* @brief Calculates the sine value based on sin8() and scales it to the given range (unsigned).
+*
+* Table of values for unscaled sin8() eg. a theta of 0 results to 128 and a theta of 255 (240+15) results to 125.
+        0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15
+    +----------------------------------------------------------------
+   0: 128 131 134 137 140 143 146 149 152 155 158 161 164 167 170 173
+  16: 177 179 182 184 187 189 192 194 197 200 202 205 207 210 212 215
+  32: 218 219 221 223 224 226 228 229 231 233 234 236 238 239 241 243
+  48: 245 245 246 246 247 248 248 249 250 250 251 251 252 253 253 254
+  64: 255 254 253 253 252 251 251 250 250 249 248 248 247 246 246 245
+  80: 245 243 241 239 238 236 234 233 231 229 228 226 224 223 221 219
+  96: 218 215 212 210 207 205 202 200 197 194 192 189 187 184 182 179
+ 112: 177 173 170 167 164 161 158 155 152 149 146 143 140 137 134 131
+ 128: 128 125 122 119 116 113 110 107 104 101  98  95  92  89  86  83
+ 144:  79  77  74  72  69  67  64  62  59  56  54  51  49  46  44  41
+ 160:  38  37  35  33  32  30  28  27  25  23  22  20  18  17  15  13
+ 176:  11  11  10  10   9   8   8   7   6   6   5   5   4   3   3   2
+ 192:   1   2   3   3   4   5   5   6   6   7   8   8   9  10  10  11
+ 208:  11  13  15  17  18  20  22  23  25  27  28  30  32  33  35  37
+ 224:  38  41  44  46  49  51  54  56  59  62  64  67  69  72  74  77
+ 240:  79  83  86  89  92  95  98 101 104 107 110 113 116 119 122 125
+*
+* @param[in] theta      Angle (a full circle mapped to 0-255) used as input for sine calculation.
+* @param[in] range_min  Lower bound of range (inclusive).
+* @param[in] range_max  Upper bound of range (inclusive).
+* @return Calculated sine value mapped to the given range.
+*/
+uint8_t scaled_sin(const uint8_t theta, const uint8_t range_min, const uint8_t range_max);
+
+/**
+* @brief Increases the given value until reaching range_max.
+* The increments occur following an upwards sine wave (scaled from range_min to range_max).
+* @param[in]        theta       Angle (a full circle mapped to 0-255) used as input for sine calculation.
+* @param[in]        range_min   Lower bound of range (inclusive).
+* @param[in]        range_max   Upper bound of range (inclusive).
+* @param[in]        max_delta   Maximum delta between value and range_max (due to values being integers and eventually not fully matching).
+* @param[in,out]    value       Reference of variable to be increased
+* @return `true` if value and range_max are within a delta of 3 (chosen by fair dice rolling), `false` otherwise
+* @see scaled_sin()
+*/
+bool scaled_sin_up(const uint8_t thea, const uint8_t range_min, const uint8_t range_max, const uint8_t max_delta, uint8_t *value);
+
+/**
+* @brief Decreases the given value until reaching range_min.
+* The decrements occur following an downwards sinus wave (scaled from range_min to range_max).
+* @param[in]        theta       Angle (a full circle mapped to 0-255) used as input for sinus calculation.
+* @param[in]        range_min   Lower bound of range (inclusive).
+* @param[in]        range_max   Upper bound of range (inclusive).
+* @param[in]        max_delta   Maximum delta between value and range_min (due to values being integers and eventually not fully matching).
+* @param[in,out]    value       Reference of variable to be decreased
+* @return `true` if value and range_max are within a delta of 3 (chosen by fair dice rolling), `false` otherwise
+* @see scaled_sin()
+*/
+bool scaled_sin_down(const uint8_t theta, const uint8_t range_min, const uint8_t range_max, const uint8_t max_delta, uint8_t *value);

+ 131 - 0
users/vitoni/vitoni.c

@@ -0,0 +1,131 @@
+// Copyright 2021 Victor Toni (@vitoni)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "vitoni.h"
+
+#include <rgb_matrix.h>
+#include <lib/lib8tion/lib8tion.h>
+
+#include "rgb_matrix_effects.h"
+#include "utils.h"
+
+#if defined(RGB_FADE_IN) || defined(RGB_DISABLE_WITH_FADE_OUT) || defined(RGB_IDLE_TIMEOUT)
+static uint8_t state;
+
+// flag used to indicate that offset calculation is needed to adjust the timer,
+// so that it matches the index used for sine calculation
+static bool calc_offset;
+
+void matrix_scan_user_rgb(void) {
+#if defined(RGB_DISABLE_WITH_FADE_OUT) || defined(RGB_IDLE_TIMEOUT)
+    const uint8_t time = rgb_time_2_scale();
+#endif
+    static uint8_t time_offset;
+
+    const uint32_t inactivity_millis = last_input_activity_elapsed();
+
+#if defined(RGB_IDLE_TIMEOUT)
+    if (IDLE != state && RGB_IDLE_TIMEOUT <= inactivity_millis) {
+        update_value(&state, IDLE_FADE_OUT, &calc_offset);
+    }
+#endif
+#if defined(RGB_DISABLE_WITH_FADE_OUT)
+    const uint32_t fade_out_duration = scale_2_rgb_time(128);
+    const uint32_t start_fade_out_after_millis = (RGB_DISABLE_TIMEOUT) > fade_out_duration
+        ? (RGB_DISABLE_TIMEOUT) - fade_out_duration
+        : 0;
+
+    if (start_fade_out_after_millis <= inactivity_millis) {
+        update_value(&state, FADE_OUT, &calc_offset);
+    }
+#elif defined(RGB_DISABLE_TIMEOUT)
+    // having to set brightness "manually" to black as starting point for fade in
+    // for the time when returning from suspended state
+    if (RGB_DISABLE_TIMEOUT <= inactivity_millis + 15) {
+        rgb_matrix_config.hsv.v = 0;
+        state = SUSPENDED;
+    }
+#endif
+
+    switch(state) {
+#if defined(RGB_IDLE_TIMEOUT)
+        case IDLE_FADE_OUT:
+            if (calc_offset) {
+                time_offset = calc_fade_out_offset(time);
+
+                // resetting flag for subsequent calls
+                calc_offset = false;
+            }
+            if (idle_fade_out(time + time_offset)) {
+                update_value(&state, IDLE, &calc_offset);
+            }
+            break;
+        case IDLE:
+#if defined(RGB_IDLE_BREATHE)
+            if (calc_offset) {
+                // no need to calculate time_offset since we are aligned already due to IDLE_FADE_OUT
+                // resetting flag for subsequent calls
+                calc_offset = false;
+            }
+            idle_breathe(time + time_offset);
+#endif
+            break;
+#endif
+#if defined(RGB_DISABLE_WITH_FADE_OUT)
+        case FADE_OUT:
+            if (calc_offset) {
+                time_offset = calc_fade_out_offset(time);
+
+                // resetting flag for subsequent calls
+                calc_offset = false;
+            }
+            if (fade_out(time + time_offset)) {
+                update_value(&state, SUSPENDED, &calc_offset);
+            }
+            break;
+#endif
+#if defined(RGB_FADE_IN) || defined(RGB_IDLE_TIMEOUT)
+        case FADE_IN:
+            {
+                // since we want to be active, fade in should be faster than e.g. fading out
+                const uint8_t fade_in_time = rgb_time_2_scale_w_factor(4);
+                if (calc_offset) {
+                    time_offset = calc_fade_in_offset(fade_in_time);
+
+                    // resetting flag for subsequent calls
+                    calc_offset = false;
+                }
+                if (fade_in(fade_in_time + time_offset)) {
+                    update_value(&state, REGULAR, &calc_offset);
+                }
+            }
+            break;
+#endif
+        default:
+            break;
+    }
+}
+
+#if defined(RGB_FADE_IN) || defined(RGB_IDLE_TIMEOUT)
+bool process_record_user_rgb(const uint16_t keycode, const keyrecord_t *record) {
+    // if we are in a non regular state we might have faded out (eventually partially)
+    // so we restore brightness (to max as we don't keep track of manually changed brightness)
+    // if (REGULAR != state && FADE_IN != state) {
+    if (FADE_IN != state && REGULAR != state) {
+        update_value(&state, FADE_IN, &calc_offset);
+    }
+
+    return true; // Process all other keycodes normally
+}
+
+void suspend_wakeup_init_user(void) {
+    if (FADE_IN != state) {
+        // setting brightness to black as starting point for fade in
+        rgb_matrix_config.hsv.v = 0;
+
+        update_value(&state, FADE_IN, &calc_offset);
+    }
+}
+#endif // defined(RGB_FADE_IN)
+
+#endif // defined(RGB_FADE_IN) || defined(RGB_DISABLE_WITH_FADE_OUT)

+ 30 - 0
users/vitoni/vitoni.h

@@ -0,0 +1,30 @@
+// Copyright 2021 Victor Toni (@vitoni)
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <quantum/action.h>
+
+#include "rgb_matrix_effects.h"
+
+/**
+ * @brief Executes periodic tasks, eg. fading or checking for upcoming supend.
+ * @details Function declaration as weak as the implementation might "disappear" depending on the RGB settings used.
+ * The weak declaration avoids having to change `keymap.c`.
+ */
+__attribute__((weak))
+void matrix_scan_user_rgb(void);
+
+/**
+ * @brief Executes tasks based on user activity, eg. fading in.
+ * @details Function declaration as weak as the implementation might "disappear" depending on the RGB settings used.
+ * The weak declaration avoids having to change `keymap.c`.
+ * @param[in]   keycode
+ * @param[in]   record
+ * @return `false` if further processing should be stopped, `true` otherwise
+ */
+__attribute__((weak))
+bool process_record_user_rgb(const uint16_t keycode, const keyrecord_t *record);