diff --git a/quantum/action_util.c b/quantum/action_util.c new file mode 100644 index 0000000..0ea8e78 --- /dev/null +++ b/quantum/action_util.c @@ -0,0 +1,573 @@ +/* +Copyright 2013 Jun Wako + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +#include "host.h" +#include "report.h" +#include "debug.h" +#include "action_util.h" +#include "action_layer.h" +#include "timer.h" +#include "keycode_config.h" +#include + +extern keymap_config_t keymap_config; + +static uint8_t real_mods = 0; +static uint8_t weak_mods = 0; + +#ifndef PROTOCOL_VUSB +static report_keyboard_t last_6kro_report; +#endif +#ifdef NKRO_ENABLE +static report_nkro_t last_nkro_report; +#endif +#ifdef KEY_OVERRIDE_ENABLE +static uint8_t weak_override_mods = 0; +static uint8_t suppressed_mods = 0; +#endif + +// TODO: pointer variable is not needed +// report_keyboard_t keyboard_report = {}; +report_keyboard_t *keyboard_report = &(report_keyboard_t){}; +#ifdef NKRO_ENABLE +report_nkro_t *nkro_report = &(report_nkro_t){}; +#endif + +extern inline void add_key(uint8_t key); +extern inline void del_key(uint8_t key); +extern inline void clear_keys(void); + +#ifndef NO_ACTION_ONESHOT +static uint8_t oneshot_mods = 0; +static uint8_t oneshot_locked_mods = 0; +uint8_t get_oneshot_locked_mods(void) { + return oneshot_locked_mods; +} +void add_oneshot_locked_mods(uint8_t mods) { + if ((oneshot_locked_mods & mods) != mods) { + oneshot_locked_mods |= mods; + oneshot_locked_mods_changed_kb(oneshot_locked_mods); + } +} +void set_oneshot_locked_mods(uint8_t mods) { + if (mods != oneshot_locked_mods) { + oneshot_locked_mods = mods; + oneshot_locked_mods_changed_kb(oneshot_locked_mods); + } +} +void clear_oneshot_locked_mods(void) { + if (oneshot_locked_mods) { + oneshot_locked_mods = 0; + oneshot_locked_mods_changed_kb(oneshot_locked_mods); + } +} +void del_oneshot_locked_mods(uint8_t mods) { + if (oneshot_locked_mods & mods) { + oneshot_locked_mods &= ~mods; + oneshot_locked_mods_changed_kb(oneshot_locked_mods); + } +} +# if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0)) +static uint16_t oneshot_time = 0; +bool has_oneshot_mods_timed_out(void) { + return TIMER_DIFF_16(timer_read(), oneshot_time) >= ONESHOT_TIMEOUT; +} +# else +bool has_oneshot_mods_timed_out(void) { + return false; +} +# endif +#endif + +/* oneshot layer */ +#ifndef NO_ACTION_ONESHOT +/** \brief oneshot_layer_data bits + * LLLL LSSS + * where: + * L => are layer bits + * S => oneshot state bits + */ +static uint8_t oneshot_layer_data = 0; + +inline uint8_t get_oneshot_layer(void) { + return oneshot_layer_data >> 3; +} +inline uint8_t get_oneshot_layer_state(void) { + return oneshot_layer_data & 0b111; +} + +# ifdef SWAP_HANDS_ENABLE +enum { + SHO_OFF, + SHO_ACTIVE, // Swap hands button was pressed, and we didn't send any swapped keys yet + SHO_PRESSED, // Swap hands button is currently pressed + SHO_USED, // Swap hands button is still pressed, and we already sent swapped keys +} swap_hands_oneshot = SHO_OFF; +# endif + +# if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0)) +static uint16_t oneshot_layer_time = 0; +inline bool has_oneshot_layer_timed_out(void) { + return TIMER_DIFF_16(timer_read(), oneshot_layer_time) >= ONESHOT_TIMEOUT && !(get_oneshot_layer_state() & ONESHOT_TOGGLED); +} +# ifdef SWAP_HANDS_ENABLE +static uint16_t oneshot_swaphands_time = 0; +inline bool has_oneshot_swaphands_timed_out(void) { + return TIMER_DIFF_16(timer_read(), oneshot_swaphands_time) >= ONESHOT_TIMEOUT && (swap_hands_oneshot == SHO_ACTIVE); +} +# endif +# endif + +# ifdef SWAP_HANDS_ENABLE + +void set_oneshot_swaphands(void) { + swap_hands_oneshot = SHO_PRESSED; + swap_hands = true; +# if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0)) + oneshot_swaphands_time = timer_read(); + if (oneshot_layer_time != 0) { + oneshot_layer_time = oneshot_swaphands_time; + } +# endif +} + +void release_oneshot_swaphands(void) { + if (swap_hands_oneshot == SHO_PRESSED) { + swap_hands_oneshot = SHO_ACTIVE; + } + if (swap_hands_oneshot == SHO_USED) { + clear_oneshot_swaphands(); + } +} + +void use_oneshot_swaphands(void) { + if (swap_hands_oneshot == SHO_PRESSED) { + swap_hands_oneshot = SHO_USED; + } + if (swap_hands_oneshot == SHO_ACTIVE) { + clear_oneshot_swaphands(); + } +} + +void clear_oneshot_swaphands(void) { + swap_hands_oneshot = SHO_OFF; + swap_hands = false; +# if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0)) + oneshot_swaphands_time = 0; +# endif +} + +# endif + +/** \brief Set oneshot layer + * + * FIXME: needs doc + */ +void set_oneshot_layer(uint8_t layer, uint8_t state) { + if (keymap_config.oneshot_enable) { + oneshot_layer_data = layer << 3 | state; + layer_on(layer); +# if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0)) + oneshot_layer_time = timer_read(); +# endif + oneshot_layer_changed_kb(get_oneshot_layer()); + } else { + layer_on(layer); + } +} +/** \brief Reset oneshot layer + * + * FIXME: needs doc + */ +void reset_oneshot_layer(void) { + oneshot_layer_data = 0; +# if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0)) + oneshot_layer_time = 0; +# endif + oneshot_layer_changed_kb(get_oneshot_layer()); +} +/** \brief Clear oneshot layer + * + * FIXME: needs doc + */ +void clear_oneshot_layer_state(oneshot_fullfillment_t state) { + uint8_t start_state = oneshot_layer_data; + oneshot_layer_data &= ~state; + if ((!get_oneshot_layer_state() && start_state != oneshot_layer_data) && keymap_config.oneshot_enable) { + layer_off(get_oneshot_layer()); + reset_oneshot_layer(); + } +} +/** \brief Is oneshot layer active + * + * FIXME: needs doc + */ +bool is_oneshot_layer_active(void) { + return get_oneshot_layer_state(); +} + +/** \brief set oneshot + * + * FIXME: needs doc + */ +void oneshot_set(bool active) { + if (keymap_config.oneshot_enable != active) { + keymap_config.oneshot_enable = active; + eeconfig_update_keymap(keymap_config.raw); + clear_oneshot_layer_state(ONESHOT_OTHER_KEY_PRESSED); + dprintf("Oneshot: active: %d\n", active); + } +} + +/** \brief toggle oneshot + * + * FIXME: needs doc + */ +void oneshot_toggle(void) { + oneshot_set(!keymap_config.oneshot_enable); +} + +/** \brief enable oneshot + * + * FIXME: needs doc + */ +void oneshot_enable(void) { + oneshot_set(true); +} + +/** \brief disable oneshot + * + * FIXME: needs doc + */ +void oneshot_disable(void) { + oneshot_set(false); +} + +bool is_oneshot_enabled(void) { + return keymap_config.oneshot_enable; +} + +#endif + +static uint8_t get_mods_for_report(void) { + uint8_t mods = real_mods | weak_mods; + +#ifndef NO_ACTION_ONESHOT + if (oneshot_mods) { +# if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0)) + if (has_oneshot_mods_timed_out()) { + dprintf("Oneshot: timeout\n"); + clear_oneshot_mods(); + } +# endif + mods |= oneshot_mods; + if (has_anykey()) { + clear_oneshot_mods(); + } + } +#endif + +#ifdef KEY_OVERRIDE_ENABLE + // These need to be last to be able to properly control key overrides + mods &= ~suppressed_mods; + mods |= weak_override_mods; +#endif + + return mods; +} + +void send_6kro_report(void) { + keyboard_report->mods = get_mods_for_report(); + +#ifdef PROTOCOL_VUSB + host_keyboard_send(keyboard_report); +#else + /* Only send the report if there are changes to propagate to the host. */ + if (memcmp(keyboard_report, &last_6kro_report, sizeof(report_keyboard_t)) != 0) { + memcpy(&last_6kro_report, keyboard_report, sizeof(report_keyboard_t)); + host_keyboard_send(keyboard_report); + } +#endif +} + +void keyboard_report_dedup_invalidate(void) { +#ifndef PROTOCOL_VUSB + memset(&last_6kro_report, 0xFF, sizeof(last_6kro_report)); +#endif +} + +#ifdef NKRO_ENABLE +void send_nkro_report(void) { + nkro_report->mods = get_mods_for_report(); + + /* Only send the report if there are changes to propagate to the host. */ + if (memcmp(nkro_report, &last_nkro_report, sizeof(report_nkro_t)) != 0) { + memcpy(&last_nkro_report, nkro_report, sizeof(report_nkro_t)); + host_nkro_send(nkro_report); + } +} + +void nkro_report_dedup_invalidate(void) { + memset(&last_nkro_report, 0xFF, sizeof(last_nkro_report)); +} +#endif + +/** \brief Send keyboard report + * + * FIXME: needs doc + */ +void send_keyboard_report(void) { +#ifdef NKRO_ENABLE + if (keyboard_protocol && keymap_config.nkro) { + send_nkro_report(); + } else { + send_6kro_report(); + } +#else + send_6kro_report(); +#endif +} + +/** \brief Get mods + * + * FIXME: needs doc + */ +uint8_t get_mods(void) { + return real_mods; +} +/** \brief add mods + * + * FIXME: needs doc + */ +void add_mods(uint8_t mods) { + real_mods |= mods; +} +/** \brief del mods + * + * FIXME: needs doc + */ +void del_mods(uint8_t mods) { + real_mods &= ~mods; +} +/** \brief set mods + * + * FIXME: needs doc + */ +void set_mods(uint8_t mods) { + real_mods = mods; +} +/** \brief clear mods + * + * FIXME: needs doc + */ +void clear_mods(void) { + real_mods = 0; +} + +/** \brief get weak mods + * + * FIXME: needs doc + */ +uint8_t get_weak_mods(void) { + return weak_mods; +} +/** \brief add weak mods + * + * FIXME: needs doc + */ +void add_weak_mods(uint8_t mods) { + weak_mods |= mods; +} +/** \brief del weak mods + * + * FIXME: needs doc + */ +void del_weak_mods(uint8_t mods) { + weak_mods &= ~mods; +} +/** \brief set weak mods + * + * FIXME: needs doc + */ +void set_weak_mods(uint8_t mods) { + weak_mods = mods; +} +/** \brief clear weak mods + * + * FIXME: needs doc + */ +void clear_weak_mods(void) { + weak_mods = 0; +} + +#ifdef KEY_OVERRIDE_ENABLE +/** \brief set weak mods used by key overrides. DO not call this manually + */ +void set_weak_override_mods(uint8_t mods) { + weak_override_mods = mods; +} +/** \brief clear weak mods used by key overrides. DO not call this manually + */ +void clear_weak_override_mods(void) { + weak_override_mods = 0; +} + +/** \brief set suppressed mods used by key overrides. DO not call this manually + */ +void set_suppressed_override_mods(uint8_t mods) { + suppressed_mods = mods; +} +/** \brief clear suppressed mods used by key overrides. DO not call this manually + */ +void clear_suppressed_override_mods(void) { + suppressed_mods = 0; +} +#endif + +#ifndef NO_ACTION_ONESHOT +/** \brief get oneshot mods + * + * FIXME: needs doc + */ +uint8_t get_oneshot_mods(void) { + return oneshot_mods; +} + +void add_oneshot_mods(uint8_t mods) { + if ((oneshot_mods & mods) != mods) { +# if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0)) + oneshot_time = timer_read(); +# endif + oneshot_mods |= mods; + oneshot_mods_changed_kb(mods); + } +} + +void del_oneshot_mods(uint8_t mods) { + if (oneshot_mods & mods) { + oneshot_mods &= ~mods; +# if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0)) + oneshot_time = oneshot_mods ? timer_read() : 0; +# endif + oneshot_mods_changed_kb(oneshot_mods); + } +} + +/** \brief set oneshot mods + * + * FIXME: needs doc + */ +void set_oneshot_mods(uint8_t mods) { + if (keymap_config.oneshot_enable) { + if (oneshot_mods != mods) { +# if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0)) + oneshot_time = timer_read(); +# endif + oneshot_mods = mods; + oneshot_mods_changed_kb(mods); + } + } +} + +/** \brief clear oneshot mods + * + * FIXME: needs doc + */ +void clear_oneshot_mods(void) { + if (oneshot_mods) { + oneshot_mods = 0; +# if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0)) + oneshot_time = 0; +# endif + oneshot_mods_changed_kb(oneshot_mods); + } +} +#endif + +/** \brief Called when the one shot modifiers have been changed. + * + * \param mods Contains the active modifiers active after the change. + */ +__attribute__((weak)) void oneshot_locked_mods_changed_user(uint8_t mods) {} + +/** \brief Called when the locked one shot modifiers have been changed. + * + * \param mods Contains the active modifiers active after the change. + */ +__attribute__((weak)) void oneshot_locked_mods_changed_kb(uint8_t mods) { + oneshot_locked_mods_changed_user(mods); +} + +/** \brief Called when the one shot modifiers have been changed. + * + * \param mods Contains the active modifiers active after the change. + */ +__attribute__((weak)) void oneshot_mods_changed_user(uint8_t mods) {} + +/** \brief Called when the one shot modifiers have been changed. + * + * \param mods Contains the active modifiers active after the change. + */ +__attribute__((weak)) void oneshot_mods_changed_kb(uint8_t mods) { + oneshot_mods_changed_user(mods); +} + +/** \brief Called when the one shot layers have been changed. + * + * \param layer Contains the layer that is toggled on, or zero when toggled off. + */ +__attribute__((weak)) void oneshot_layer_changed_user(uint8_t layer) {} + +/** \brief Called when the one shot layers have been changed. + * + * \param layer Contains the layer that is toggled on, or zero when toggled off. + */ +__attribute__((weak)) void oneshot_layer_changed_kb(uint8_t layer) { + oneshot_layer_changed_user(layer); +} + +/** \brief inspect keyboard state + * + * FIXME: needs doc + */ +uint8_t has_anymod(void) { + return bitpop(real_mods); +} + +#ifdef DUMMY_MOD_NEUTRALIZER_KEYCODE +/** \brief Send a dummy keycode in between the register and unregister event of a modifier key, to neutralize the "flashing modifiers" phenomenon. + * + * \param active_mods 8-bit packed bit-array describing the currently active modifiers (in the format GASCGASC). + * + * Certain QMK features like key overrides or retro tap must unregister a previously + * registered modifier before sending another keycode but this can trigger undesired + * keyboard shortcuts if the clean tap of a single modifier key is bound to an action + * on the host OS, as is for example the case for the left GUI key on Windows, which + * opens the Start Menu when tapped. + */ +void neutralize_flashing_modifiers(uint8_t active_mods) { + // In most scenarios, the flashing modifiers phenomenon is a problem + // only for a subset of modifier masks. + const static uint8_t mods_to_neutralize[] = MODS_TO_NEUTRALIZE; + const static uint8_t n_mods = ARRAY_SIZE(mods_to_neutralize); + for (uint8_t i = 0; i < n_mods; ++i) { + if (active_mods == mods_to_neutralize[i]) { + tap_code(DUMMY_MOD_NEUTRALIZER_KEYCODE); + break; + } + } +} +#endif diff --git a/quantum/action_util.h b/quantum/action_util.h new file mode 100644 index 0000000..b2bfe81 --- /dev/null +++ b/quantum/action_util.h @@ -0,0 +1,127 @@ +/* +Copyright 2013 Jun Wako + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#pragma once + +#include +#include "report.h" +#include "modifiers.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern report_keyboard_t *keyboard_report; +#ifdef NKRO_ENABLE +extern report_nkro_t *nkro_report; +#endif + +void send_keyboard_report(void); +void keyboard_report_dedup_invalidate(void); +#ifdef NKRO_ENABLE +void nkro_report_dedup_invalidate(void); +#endif + +/* key */ +inline void add_key(uint8_t key) { + add_key_to_report(key); +} + +inline void del_key(uint8_t key) { + del_key_from_report(key); +} + +inline void clear_keys(void) { + clear_keys_from_report(); +} + +/* modifier */ +uint8_t get_mods(void); +void add_mods(uint8_t mods); +void del_mods(uint8_t mods); +void set_mods(uint8_t mods); +void clear_mods(void); + +/* weak modifier */ +uint8_t get_weak_mods(void); +void add_weak_mods(uint8_t mods); +void del_weak_mods(uint8_t mods); +void set_weak_mods(uint8_t mods); +void clear_weak_mods(void); + +/* oneshot modifier */ +uint8_t get_oneshot_mods(void); +void add_oneshot_mods(uint8_t mods); +void del_oneshot_mods(uint8_t mods); +void set_oneshot_mods(uint8_t mods); +void clear_oneshot_mods(void); +bool has_oneshot_mods_timed_out(void); + +uint8_t get_oneshot_locked_mods(void); +void add_oneshot_locked_mods(uint8_t mods); +void set_oneshot_locked_mods(uint8_t mods); +void clear_oneshot_locked_mods(void); +void del_oneshot_locked_mods(uint8_t mods); + +typedef enum { ONESHOT_PRESSED = 0b01, ONESHOT_OTHER_KEY_PRESSED = 0b10, ONESHOT_START = 0b11, ONESHOT_TOGGLED = 0b100 } oneshot_fullfillment_t; +void set_oneshot_layer(uint8_t layer, uint8_t state); +uint8_t get_oneshot_layer(void); +void clear_oneshot_layer_state(oneshot_fullfillment_t state); +void reset_oneshot_layer(void); +bool is_oneshot_layer_active(void); +uint8_t get_oneshot_layer_state(void); +bool has_oneshot_layer_timed_out(void); +bool has_oneshot_swaphands_timed_out(void); + +void oneshot_locked_mods_changed_user(uint8_t mods); +void oneshot_locked_mods_changed_kb(uint8_t mods); +void oneshot_mods_changed_user(uint8_t mods); +void oneshot_mods_changed_kb(uint8_t mods); +void oneshot_layer_changed_user(uint8_t layer); +void oneshot_layer_changed_kb(uint8_t layer); + +void oneshot_toggle(void); +void oneshot_enable(void); +void oneshot_disable(void); +bool is_oneshot_enabled(void); + +/* inspect */ +uint8_t has_anymod(void); + +#ifdef SWAP_HANDS_ENABLE +void set_oneshot_swaphands(void); +void release_oneshot_swaphands(void); +void use_oneshot_swaphands(void); +void clear_oneshot_swaphands(void); +#endif + +#ifdef DUMMY_MOD_NEUTRALIZER_KEYCODE +// KC_A is used as the lowerbound instead of QK_BASIC because the range QK_BASIC...KC_A includes +// internal keycodes like KC_NO and KC_TRANSPARENT which are unsuitable for use with `tap_code(kc)`. +# if !(KC_A <= DUMMY_MOD_NEUTRALIZER_KEYCODE && DUMMY_MOD_NEUTRALIZER_KEYCODE <= QK_BASIC_MAX) +# error "DUMMY_MOD_NEUTRALIZER_KEYCODE must be a basic, unmodified, HID keycode!" +# endif +void neutralize_flashing_modifiers(uint8_t active_mods); +#endif +#ifndef MODS_TO_NEUTRALIZE +# define MODS_TO_NEUTRALIZE \ + { MOD_BIT(KC_LEFT_ALT), MOD_BIT(KC_LEFT_GUI) } +#endif + +#ifdef __cplusplus +} +#endif diff --git a/tide75/control/control.c b/tide75/control/control.c new file mode 100644 index 0000000..e135ed2 --- /dev/null +++ b/tide75/control/control.c @@ -0,0 +1,301 @@ +// Copyright 2024 SDK (@sdk66) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "control.h" +#include "suspend.h" + +static ioline_t col_pins[MATRIX_COLS] = MATRIX_COL_PINS; +extern bool lower_sleep; + +/* Forward declarations for weak hooks defined in lowpower.c */ +void lpwr_presleep_hook(void); +void lpwr_wakeup_hook(void); +void last_matrix_activity_trigger(void); + +/* ─── LPWR overrides ───────────────────────────────────────────────── + * + * ROOT CAUSE of Cmd+Shift+4 modifier drop after idle sleep: + * + * The default lpwr_wakeup_cb() (lowpower.c) calls suspend_wakeup_init() + * which calls clear_mods() / clear_weak_mods() / clear_keys(). This + * fires ~200ms after MCU wake (LPWR_WAKEUP_DELAY), by which time the + * main loop has already scanned the wake-up keys and registered their + * modifiers. The clear_mods() call then destroys that modifier state. + * + * Because held keys are not re-processed by the matrix (debounce sees + * them as already pressed), the modifiers are never re-registered. + * When the wireless module finally delivers the next report, mods=0 + * and e.g. Cmd+Shift+4 becomes Shift+4 = '$' (Cmd lost). + * + * The keyboard only enters sleep after 1 minute of idle (no keys + * held), so there is no stale state to clear on wakeup. We override + * both lpwr_presleep_cb() and lpwr_wakeup_cb() to: + * - Track the RGB enabled state ourselves (rgb_enable_bak is static + * in lowpower.c, inaccessible from here) + * - Skip suspend_wakeup_init() entirely on wakeup + * - Still call suspend_wakeup_init_quantum() so the quantum layer + * can restore LED indicators and RGB matrix suspend state + */ +static bool tide75_rgb_backup = false; + +void lpwr_presleep_cb(void) { +#if defined(RGB_MATRIX_ENABLE) + tide75_rgb_backup = rgb_matrix_is_enabled(); + rgb_matrix_disable_noeeprom(); +#elif defined(RGBLIGHT_ENABLE) + tide75_rgb_backup = rgblight_is_enabled(); + rgblight_disable_noeeprom(); +#else + tide75_rgb_backup = false; +#endif + suspend_power_down(); + lpwr_presleep_hook(); +} + +void lpwr_wakeup_cb(void) { + if (tide75_rgb_backup) { +#if defined(RGB_MATRIX_ENABLE) + rgb_matrix_enable_noeeprom(); +#elif defined(RGBLIGHT_ENABLE) + rgblight_enable_noeeprom(); +#endif + } + + /* Skip suspend_wakeup_init() — it calls clear_mods() which destroys + * modifier state from the wake-up keypress. Call only the quantum- + * level wakeup (restores LED indicators, sets RGB matrix suspend + * state to false, calls suspend_wakeup_init_kb()). */ + suspend_wakeup_init_quantum(); + + lpwr_wakeup_hook(); + last_matrix_activity_trigger(); +} + +bool hs_modeio_detection(bool update, uint8_t *mode,uint8_t lsat_btdev) { + static uint32_t scan_timer = 0x00; + + if ((update != true) && (timer_elapsed32(scan_timer) <= (HS_MODEIO_DETECTION_TIME))) { + return false; + } + scan_timer = timer_read32(); + +#if defined(HS_BT_DEF_PIN) && defined(HS_2G4_DEF_PIN) + uint8_t now_mode = 0x00; + uint8_t hs_mode = 0x00; + static uint8_t last_mode = 0x00; + bool sw_mode = false; + + now_mode = (HS_GET_MODE_PIN(HS_USB_PIN_STATE) ? 3 : (HS_GET_MODE_PIN(HS_BT_PIN_STATE) ? 1 : ((HS_GET_MODE_PIN(HS_2G4_PIN_STATE) ? 2 : 0)))); + hs_mode = (*mode >= DEVS_BT1 && *mode <= DEVS_BT5) ? 1 : ((*mode == DEVS_2G4) ? 2 : ((*mode == DEVS_USB) ? 3 : 0)); + sw_mode = ((update || (last_mode == now_mode)) && (hs_mode != now_mode)) ? true : false; + last_mode = now_mode; + + switch (now_mode) + { + case 1: + *mode = hs_bt; + if (sw_mode) { + wireless_devs_change(wireless_get_current_devs(), lsat_btdev, false); + + } + break; + case 2: + *mode = hs_2g4; + if (sw_mode) { + wireless_devs_change(wireless_get_current_devs(), DEVS_2G4, false); + + } + break; + case 3: + *mode = hs_usb; + if (sw_mode) + wireless_devs_change(wireless_get_current_devs(), DEVS_USB, false); + + break; + default: + break; + } + + if (sw_mode) { + hs_rgb_blink_set_timer(timer_read32()); + suspend_wakeup_init(); + return true; + } +#else + *mode = hs_none; +#endif + + return false; +} + +static uint32_t hs_linker_rgb_timer = 0x00; + +bool hs_mode_scan(bool update,uint8_t moude,uint8_t lsat_btdev) { + + if (hs_modeio_detection(update, &moude, lsat_btdev)) { + + return true; + } + hs_rgb_blink_hook(); + return false; +} + +void hs_rgb_blink_set_timer(uint32_t time) { + hs_linker_rgb_timer = time; +} + +uint32_t hs_rgb_blink_get_timer(void) { + return hs_linker_rgb_timer; +} + +bool hs_rgb_blink_hook(){ + static uint8_t last_status; + + if (last_status != *md_getp_state()){ + last_status = *md_getp_state(); + hs_rgb_blink_set_timer(0x00); + } + + switch (*md_getp_state()) + { + case MD_STATE_NONE: { + hs_rgb_blink_set_timer(0x00); + } break; + + case MD_STATE_DISCONNECTED: + if (hs_rgb_blink_get_timer() == 0x00){ + hs_rgb_blink_set_timer(timer_read32()); + extern void wireless_devs_change_kb(uint8_t old_devs, uint8_t new_devs, bool reset); + wireless_devs_change_kb(wireless_get_current_devs(),wireless_get_current_devs(),false); + } + else{ + if (timer_elapsed32(hs_rgb_blink_get_timer()) >= HS_LBACK_TIMEOUT) { + hs_rgb_blink_set_timer(timer_read32()); + md_send_devctrl(MD_SND_CMD_DEVCTRL_USB); + wait_ms(200); + lpwr_set_timeout_manual(true); + + } + } + case MD_STATE_CONNECTED: + if (hs_rgb_blink_get_timer() == 0x00){ + hs_rgb_blink_set_timer(timer_read32()); + } + else{ + if (timer_elapsed32(hs_rgb_blink_get_timer()) >= HS_SLEEP_TIMEOUT) { + hs_rgb_blink_set_timer(timer_read32()); + lpwr_set_timeout_manual(true); + } + } + default: + break; + } + return true; +} + +void lpwr_exti_init_hook(void){ + +#ifdef HS_BT_DEF_PIN + setPinInputHigh(HS_BT_DEF_PIN); + waitInputPinDelay(); + palEnableLineEvent(HS_BT_DEF_PIN, PAL_EVENT_MODE_BOTH_EDGES); +#endif + +#ifdef HS_2G4_DEF_PIN + setPinInputHigh(HS_2G4_DEF_PIN); + waitInputPinDelay(); + palEnableLineEvent(HS_2G4_DEF_PIN, PAL_EVENT_MODE_BOTH_EDGES); +#endif + + if (lower_sleep){ +#if DIODE_DIRECTION == ROW2COL + for (uint8_t i = 0; i < ARRAY_SIZE(col_pins); i++) { + if (col_pins[i] != NO_PIN) { + setPinOutput(col_pins[i]); + writePinHigh(col_pins[i]); + } + } +#endif + } + setPinInput(HS_BAT_CABLE_PIN); + waitInputPinDelay(); + palEnableLineEvent(HS_BAT_CABLE_PIN, PAL_EVENT_MODE_RISING_EDGE); +} + +void palcallback_cb(uint8_t line){ + switch (line) { + case PAL_PAD(HS_BAT_CABLE_PIN): { + lpwr_set_sleep_wakeupcd(LPWR_WAKEUP_CABLE); + } break; +#ifdef HS_2G4_DEF_PIN + case PAL_PAD(HS_2G4_DEF_PIN): { + lpwr_set_sleep_wakeupcd(LPWR_WAKEUP_SWITCH); + } break; +#endif + +#ifdef HS_BT_DEF_PIN + case PAL_PAD(HS_BT_DEF_PIN): { + lpwr_set_sleep_wakeupcd(LPWR_WAKEUP_SWITCH); + } break; +#endif + default: { + + } break; + } +} + + + +void lpwr_stop_hook_post(void){ + if (lower_sleep){ + switch (lpwr_get_sleep_wakeupcd()) { + case LPWR_WAKEUP_USB: + case LPWR_WAKEUP_CABLE: { + lower_sleep = false; + lpwr_set_state(LPWR_WAKEUP); + } break; + default: { + lpwr_set_state(LPWR_STOP); + } break; + } + return; /* Low-battery path: don't prime radio */ + } + + /* Prime the wireless radio after waking from idle sleep. + * + * After sleep the radio link to the dongle is cold. The first keypress + * wakes the MCU but its HID report is sent before the radio + * re-establishes, so the modifier (e.g. Cmd in Cmd+C) is lost. + * + * The working wireless implementation (keyboards/wireless/) calls + * wireless_devs_change() on wakeup to re-issue the device-mode command, + * forcing the module to re-establish the radio link. The linker + * implementation used by the TIDE 75 never does this. + * + * This runs inside lpwr_stop_cb() right after lpwr_enter_stop() returns + * — before the main loop resumes and before matrix_scan() processes the + * wake-up keypress. + */ + uint8_t devs = wireless_get_current_devs(); + if (devs == DEVS_USB) { + return; + } + + /* Re-issue the device-mode command to wake the module radio. + * For 2.4G this sends MD_SND_CMD_DEVCTRL_2G4 (0x30), for BT it sends + * the matching BT channel command. This is what Lofree/P75 keyboards + * do in their wakeup path via wireless_devs_change(). + */ + wireless_devs_change(devs, devs, false); + + /* Drain UART queue (~35ms) to process the devctrl command. + * The post-sleep resync mechanism (15ms intervals, 1200ms window) + * re-delivers the first report once the radio warms up (~60-80ms). + * No gate is needed — modifiers are preserved by our lpwr_wakeup_cb() + * override which skips the destructive clear_mods() call. + */ + for (uint8_t i = 0; i < 7; i++) { + md_main_task(); + wait_ms(5); + } +} diff --git a/tide75/control/control.h b/tide75/control/control.h new file mode 100644 index 0000000..2cd4f06 --- /dev/null +++ b/tide75/control/control.h @@ -0,0 +1,35 @@ +// Copyright 2024 SDK (@sdk66) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include QMK_KEYBOARD_H + +#include +#include "wireless.h" + +/* Use when there is a three-stage dial switch */ +// # define HS_BT_DEF_PIN C14 +// # define HS_2G4_DEF_PIN C15 +// # define HS_BT_PIN_STATE 0, 1 +// # define HS_2G4_PIN_STATE 1, 0 +// # define HS_USB_PIN_STATE 1, 1 + +# define HS_GET_MODE_PIN_(pin_bt, pin_2g4) ((((#pin_bt)[0] == 'x') || ((readPin(HS_BT_DEF_PIN) + 0x30) == ((#pin_bt)[0]))) && (((#pin_2g4)[0] == 'x') || ((readPin(HS_2G4_DEF_PIN) + 0x30) == ((#pin_2g4)[0])))) +# define HS_GET_MODE_PIN(state) HS_GET_MODE_PIN_(state) +# define HS_MODEIO_DETECTION_TIME 50 + +/* Set the wireless device's reconnect timeout and connection usage sleep time */ +#define HS_LBACK_TIMEOUT (30 * 1000) +#define HS_SLEEP_TIMEOUT (1 * 60000) + +enum modeio_mode { + hs_none = 0, + hs_usb, + hs_bt, + hs_2g4, + hs_wireless +}; + +bool hs_rgb_blink_hook(void); +bool hs_mode_scan(bool update,uint8_t moude,uint8_t lsat_btdev); +bool hs_modeio_detection(bool update, uint8_t *mode,uint8_t lsat_btdev); +void hs_rgb_blink_set_timer(uint32_t time); \ No newline at end of file diff --git a/tide75/info.json b/tide75/info.json index 5beea73..30560ab 100644 --- a/tide75/info.json +++ b/tide75/info.json @@ -4,8 +4,10 @@ "maintainer": "sdk66", "bootloader": "wb32-dfu", "bootmagic": { + "enabled": true, "matrix": [0, 0] }, + "debounce": 8, "diode_direction": "ROW2COL", "encoder": { "rotary": [ @@ -164,7 +166,7 @@ "url": "", "usb": { "device_version": "0.0.1", - "force_nkro": true, + "force_nkro": false, "pid": "0xE471", "suspend_wakeup_delay": 1000, "vid": "0x342D" diff --git a/tide75/linker/wireless/transport.c b/tide75/linker/wireless/transport.c index 83e30c7..79590b7 100644 --- a/tide75/linker/wireless/transport.c +++ b/tide75/linker/wireless/transport.c @@ -120,15 +120,21 @@ void usb_remote_wakeup(void) { } #else static uint32_t suspend_timer = 0x00; + static bool suspended = false; if ((USB_DRIVER.state == USB_SUSPENDED)) { if (!suspend_timer) suspend_timer = sync_timer_read32(); if (sync_timer_elapsed32(suspend_timer) >= USB_POWER_DOWN_DELAY) { suspend_timer = 0x00; + suspended = true; suspend_power_down(); } } else { suspend_timer = 0x00; + if (suspended) { + suspended = false; + suspend_wakeup_init(); + } } #endif } diff --git a/tide75/linker/wireless/wireless.c b/tide75/linker/wireless/wireless.c index 8a2cba6..128bf1b 100644 --- a/tide75/linker/wireless/wireless.c +++ b/tide75/linker/wireless/wireless.c @@ -9,7 +9,32 @@ # define WLS_INQUIRY_BAT_TIME 3000 #endif +#ifndef WLS_KEEPALIVE_INTERVAL +# define WLS_KEEPALIVE_INTERVAL 1000 +#endif + +#ifndef WLS_POSTSLEEP_RESYNC_DELAY +# define WLS_POSTSLEEP_RESYNC_DELAY 15 +#endif + +#ifndef WLS_POSTSLEEP_RESYNC_COUNT +# define WLS_POSTSLEEP_RESYNC_COUNT 80 +#endif + +#ifndef WLS_POSTSLEEP_GATE_MS +# define WLS_POSTSLEEP_GATE_MS 100 +#endif + static uint8_t wls_devs = DEVS_USB; +static bool report_dropped = false; +static uint32_t keepalive_timer = 0; + +static bool postsleep_resync = false; +static uint32_t postsleep_timer = 0; +static uint8_t postsleep_count = 0; + +static bool postsleep_gate = false; +static uint32_t postsleep_gate_timer = 0; void last_matrix_activity_trigger(void); @@ -45,10 +70,52 @@ void wireless_send_keyboard(report_keyboard_t *report) { uint8_t wls_report_kb[MD_SND_CMD_KB_LEN] = {0}; if (*md_getp_state() != MD_STATE_CONNECTED) { + report_dropped = true; wireless_devs_change(wls_devs, wls_devs, false); return; } + /* When waking from sleep (or during presleep/stop transitions) the + * radio link is cold. The first report will likely be lost during + * radio/USB re-establishment. Schedule aggressive resyncs to + * re-deliver the keyboard state once the link is warm. + * 1200ms of coverage (80 × 15ms) handles even slow USB resume. + * + * Guard with !postsleep_resync so subsequent calls (including those + * triggered by the resync itself via host_keyboard_send) don't reset + * the counter and timer. Without this guard the counter never + * advances during the 200ms LPWR_WAKEUP window. + */ + lpwr_state_t state = lpwr_get_state(); + if ((state == LPWR_WAKEUP || state == LPWR_STOP || state == LPWR_PRESLEEP) + && !postsleep_resync) { + postsleep_resync = true; + postsleep_count = 0; + postsleep_timer = sync_timer_read32(); + } + + /* Post-sleep gate: suppress radio sends while the 2.4GHz link is cold. + * + * After waking from sleep the radio module needs ~60-80ms to + * re-establish the link to the dongle. Reports sent during this + * cold window are ACK'd by the module (UART level) but lost or + * corrupted over the air — causing modifier drops (e.g. Cmd+Shift+4 + * registers as Shift+4 = '$'). + * + * The gate prevents any md_send_kb() calls for WLS_POSTSLEEP_GATE_MS + * (100ms) after wake. The main loop runs freely during this time, + * so keyboard_task() still scans the matrix and updates the global + * keyboard_report. When the gate lifts in wireless_task(), the + * accumulated report (with all modifiers) is delivered to a warm + * radio link. + * + * The postsleep_resync mechanism (armed above) provides 1200ms of + * continued re-delivery as additional insurance. + */ + if (postsleep_gate) { + return; + } + if (report != NULL) { memcpy(wls_report_kb, (uint8_t *)report, sizeof(wls_report_kb)); } @@ -60,6 +127,7 @@ void wireless_send_nkro(report_nkro_t *report) { uint8_t wls_report_nkro[MD_SND_CMD_NKRO_LEN] = {0}; if (*md_getp_state() != MD_STATE_CONNECTED) { + report_dropped = true; wireless_devs_change(wls_devs, wls_devs, false); return; } @@ -126,7 +194,37 @@ void wireless_send_nkro(report_nkro_t *report) { } wireless_driver.send_keyboard(&temp_report_keyboard); - md_send_nkro(wls_report_nkro); + + /* Skip the NKRO overflow during post-sleep resync. + * + * The 0xA2 NKRO message contains only the key bitmap (no modifiers). + * During the radio warm-up window the 0xA1 (6KRO, with modifiers) and + * 0xA2 (NKRO, without modifiers) arrive as separate radio packets. + * If the dongle processes the 0xA2 after the 0xA1, it overwrites the + * modifier state — causing e.g. Cmd+Shift+4 to register as Shift+4 + * ('$') with Cmd lost. + * + * During the resync window, send ONLY the 6KRO message which contains + * both modifiers and up to 6 keycodes. This is sufficient for all + * modifier-based shortcuts. Full NKRO resumes after the resync + * window closes (~1200ms). + * + * Outside the resync window, only send the NKRO overflow when the + * bitmap actually contains data (>6 keys pressed). An empty 0xA2 + * can still overwrite 6KRO modifier state on the dongle. + */ + if (!postsleep_resync) { + bool has_nkro_overflow = false; + for (uint8_t i = 0; i < MD_SND_CMD_NKRO_LEN; i++) { + if (wls_report_nkro[i]) { + has_nkro_overflow = true; + break; + } + } + if (has_nkro_overflow) { + md_send_nkro(wls_report_nkro); + } + } } void wireless_send_mouse(report_mouse_t *report) { @@ -211,6 +309,11 @@ uint8_t wireless_get_current_devs(void) { return wls_devs; } +void wireless_set_postsleep_gate(void) { + postsleep_gate = true; + postsleep_gate_timer = sync_timer_read32(); +} + void wireless_pre_task(void) __attribute__((weak)); void wireless_pre_task(void) {} @@ -224,6 +327,125 @@ void wireless_task(void) { md_main_task(); wireless_post_task(); + /* Post-sleep gate expiry: once the radio has had enough time to + * warm up, lift the gate so the next keyboard_task() can deliver + * the report through its normal path (which correctly sets + * modifier state via get_mods_for_report()). + * + * We do NOT send directly here — keyboard_task() knows whether + * to use the 6KRO or NKRO path and sets the modifier byte in + * the correct report object. Sending directly from here risks + * a path mismatch (e.g. NKRO active but reading 6KRO report + * whose mods were never populated). + * + * Invalidate BOTH dedup caches so the next keyboard_task() + * is guaranteed to send regardless of which protocol is active. + */ + if (postsleep_gate && + sync_timer_elapsed32(postsleep_gate_timer) >= WLS_POSTSLEEP_GATE_MS) { + postsleep_gate = false; + + keyboard_report_dedup_invalidate(); +#ifdef NKRO_ENABLE + nkro_report_dedup_invalidate(); +#endif + } + + /* Resync after module reconnection. + * When reports are dropped because the module was not connected, + * QMK's report dedup cache still considers them "sent." Invalidate + * the cache and force a resend. + */ + if (report_dropped && *md_getp_state() == MD_STATE_CONNECTED) { + report_dropped = false; + keepalive_timer = sync_timer_read32(); + +#ifdef NKRO_ENABLE + extern keymap_config_t keymap_config; + if (keyboard_protocol && keymap_config.nkro) { + nkro_report_dedup_invalidate(); + } else +#endif + { + keyboard_report_dedup_invalidate(); + } + + extern report_keyboard_t *keyboard_report; +#ifdef NKRO_ENABLE + extern report_nkro_t *nkro_report; + if (keyboard_protocol && keymap_config.nkro) { + host_nkro_send(nkro_report); + } else +#endif + { + host_keyboard_send(keyboard_report); + } + } + + /* Post-sleep resync: force-resend keyboard state at 100ms intervals + * after waking from sleep. The first report is sent immediately by + * wireless_send_keyboard() but is typically lost while the radio/USB + * link re-establishes. These resyncs re-deliver the state once the + * link is warm. + */ + if (postsleep_resync && *md_getp_state() == MD_STATE_CONNECTED && + sync_timer_elapsed32(postsleep_timer) >= WLS_POSTSLEEP_RESYNC_DELAY) { + postsleep_timer = sync_timer_read32(); + + if (++postsleep_count >= WLS_POSTSLEEP_RESYNC_COUNT) { + postsleep_resync = false; + } + +#ifdef NKRO_ENABLE + extern keymap_config_t keymap_config; + if (keyboard_protocol && keymap_config.nkro) { + nkro_report_dedup_invalidate(); + } else +#endif + { + keyboard_report_dedup_invalidate(); + } + + extern report_keyboard_t *keyboard_report; +#ifdef NKRO_ENABLE + extern report_nkro_t *nkro_report; + if (keyboard_protocol && keymap_config.nkro) { + host_nkro_send(nkro_report); + } else +#endif + { + host_keyboard_send(keyboard_report); + } + } + + /* Periodic keep-alive: re-send the current keyboard state to prevent + * the 2.4GHz radio link and dongle USB from entering power-saving + * modes. This replaces the reactive idle-detection resync which + * failed to reliably recover the first report after idle. + * + * Does NOT reset the input activity timer, so the normal 5-minute + * sleep timeout still works as designed. + */ + if (get_transport() == TRANSPORT_WLS && + *md_getp_state() == MD_STATE_CONNECTED && + lpwr_get_state() == LPWR_NORMAL && + sync_timer_elapsed32(keepalive_timer) >= WLS_KEEPALIVE_INTERVAL) { + + keepalive_timer = sync_timer_read32(); + + extern report_keyboard_t *keyboard_report; +#ifdef NKRO_ENABLE + extern report_nkro_t *nkro_report; + extern keymap_config_t keymap_config; + if (keyboard_protocol && keymap_config.nkro) { + host_nkro_send(nkro_report); + } else +#endif + { + host_keyboard_send(keyboard_report); + } + } + /* usb_remote_wakeup() should be invoked last so that we have chance * to switch to wireless after start-up when usb is not connected */ diff --git a/tide75/linker/wireless/wireless.h b/tide75/linker/wireless/wireless.h index 683347b..77f2aff 100644 --- a/tide75/linker/wireless/wireless.h +++ b/tide75/linker/wireless/wireless.h @@ -10,5 +10,6 @@ void wireless_init(void); void wireless_devs_change(uint8_t old_devs, uint8_t new_devs, bool reset); uint8_t wireless_get_current_devs(void); +void wireless_set_postsleep_gate(void); void wireless_pre_task(void); void wireless_post_task(void); diff --git a/tide75/tide75.c b/tide75/tide75.c index 7b7c7af..7c92cd6 100644 --- a/tide75/tide75.c +++ b/tide75/tide75.c @@ -1,10 +1,1157 @@ -// Copyright 2024 SDK (@sdk66) -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "quantum.h" - -void keyboard_pre_init_kb(void) { - gpio_set_pin_output(A5); - gpio_write_pin_high(A5); - keyboard_pre_init_user(); -} +// Copyright 2024 SDK (@sdk66) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include QMK_KEYBOARD_H +#include "control/control.h" +#include "rgb_record/rgb_record.h" +#include "rgb_record/rgb_rgblight.h" + + +#ifdef WIRELESS_ENABLE +# include "wireless.h" +# include "usb_main.h" +# include "lowpower.h" +#endif + +typedef union { + uint32_t raw; + struct { + uint8_t flag : 1; + uint8_t devs : 3; + uint8_t record_channel : 4; + uint8_t record_last_mode; + uint8_t last_btdevs : 3; + }; +} confinfo_t; +confinfo_t confinfo; + +typedef struct { + bool active; + uint32_t timer; + uint32_t interval; + uint32_t times; + uint8_t index; + RGB rgb; + void (*blink_cb)(uint8_t); +} hs_rgb_indicator_t; + +enum layers { + _BL = 0, + _FL, + _MBL, + _MFL, + _DEFA +}; + +hs_rgb_indicator_t hs_rgb_indicators[HS_RGB_INDICATOR_COUNT]; +hs_rgb_indicator_t hs_rgb_bat[HS_RGB_BAT_COUNT]; + +void rgb_blink_dir(void); +void hs_reset_settings(void); +void rgb_matrix_hs_indicator(void); +void rgb_matrix_hs_indicator_set(uint8_t index, RGB rgb, uint32_t interval, uint8_t times); +void rgb_matrix_hs_set_remain_time(uint8_t index, uint8_t remain_time); + + +#define keymap_is_mac_system() (get_highest_layer(default_layer_state) == _MBL) + +uint32_t post_init_timer = 0x00; +bool inqbat_flag = false; +bool mac_status = false; +bool charging_state = false; +bool bat_full_flag = false; +bool enable_bat_indicators = true; +uint32_t bat_indicator_cnt = true; +static uint32_t ee_clr_timer = 0; +static uint32_t rec_time; +static bool rec_filp; +bool test_white_light_flag = false; +HSV start_hsv; +bool no_record_fg; +bool lower_sleep = false; +uint8_t buff[] = {14, 8, 2, 1, 1, 1, 1, 1, 1, 1, 0}; + +void eeconfig_confinfo_update(uint32_t raw) { + + eeconfig_update_kb(raw); +} + +uint32_t eeconfig_confinfo_read(void) { + + return eeconfig_read_kb(); +} + +void eeconfig_confinfo_default(void) { + + confinfo.flag = true; + confinfo.record_channel = 0; + confinfo.record_last_mode = 0xff; + confinfo.last_btdevs = 1; +#ifdef WIRELESS_ENABLE + confinfo.devs = DEVS_USB; +#endif + + eeconfig_init_user_datablock(); + eeconfig_confinfo_update(confinfo.raw); +#ifdef RGBLIGHT_ENABLE + rgblight_mode(buff[0]); +#endif +} + +void eeconfig_confinfo_init(void) { + + confinfo.raw = eeconfig_confinfo_read(); + if (!confinfo.raw) { + eeconfig_confinfo_default(); + } +} + +void lpwr_stop_hook_pre(void){ + + gpio_write_pin_low(LED_POWER_EN_PIN); + +#ifdef HS_LED_BOOSTING_PIN + gpio_write_pin_low(HS_LED_BOOSTING_PIN); +#endif + + if (lower_sleep){ + md_send_devctrl(MD_SND_CMD_DEVCTRL_USB); + wait_ms(200); + lpwr_set_sleep_wakeupcd(LPWR_WAKEUP_UART); + } +} + +void lpwr_wakeup_hook(void){ + hs_mode_scan(false,confinfo.devs,confinfo.last_btdevs); + + gpio_write_pin_high(LED_POWER_EN_PIN); +#ifdef HS_LED_BOOSTING_PIN + gpio_write_pin_high(HS_LED_BOOSTING_PIN); +#endif + +} + +void keyboard_post_init_kb(void) { + +#ifdef CONSOLE_ENABLE + debug_enable = true; +#endif + + eeconfig_confinfo_init(); + +#ifdef LED_POWER_EN_PIN + gpio_set_pin_output(LED_POWER_EN_PIN); + gpio_write_pin_high(LED_POWER_EN_PIN); + +#ifdef HS_LED_BOOSTING_PIN + gpio_set_pin_output(HS_LED_BOOSTING_PIN); + gpio_write_pin_high(HS_LED_BOOSTING_PIN); +#endif + +#ifdef MM_BT_DEF_PIN + setPinInputHigh(MM_BT_DEF_PIN); +#endif + +#ifdef MM_2G4_DEF_PIN + setPinInputHigh(MM_2G4_DEF_PIN); +#endif + +#endif + +#ifdef USB_POWER_EN_PIN + gpio_write_pin_low(USB_POWER_EN_PIN); + gpio_set_pin_output(USB_POWER_EN_PIN); +#endif + +#ifdef HS_BAT_CABLE_PIN + setPinInput(HS_BAT_CABLE_PIN); +#endif + +#ifdef BAT_FULL_PIN + setPinInputHigh(BAT_FULL_PIN); +#endif + +#ifdef WIRELESS_ENABLE + wireless_init(); +#if !(defined(MM_BT_DEF_PIN) && defined(MM_2G4_DEF_PIN)) + wireless_devs_change(!confinfo.devs, confinfo.devs, false); +#endif + post_init_timer = timer_read32(); +#endif + + keyboard_post_init_user(); + + rgbrec_init(confinfo.record_channel); + + start_hsv = rgb_matrix_get_hsv(); +} + +#ifdef WIRELESS_ENABLE + +void usb_power_connect(void) { + +# ifdef USB_POWER_EN_PIN + gpio_write_pin_low(USB_POWER_EN_PIN); +# endif +} + +void usb_power_disconnect(void) { + +# ifdef USB_POWER_EN_PIN + gpio_write_pin_high(USB_POWER_EN_PIN); +# endif +} + +void suspend_power_down_kb(void) { + +# ifdef LED_POWER_EN_PIN + gpio_write_pin_low(LED_POWER_EN_PIN); +# endif + + suspend_power_down_user(); + +} + +void suspend_wakeup_init_kb(void) { + +# ifdef LED_POWER_EN_PIN + gpio_write_pin_high(LED_POWER_EN_PIN); +# endif + + suspend_wakeup_init_user(); + hs_rgb_blink_set_timer(timer_read32()); +} + +bool lpwr_is_allow_timeout_hook(void) { + + if (wireless_get_current_devs() == DEVS_USB) { + return false; + } + return true; +} + +void wireless_post_task(void) { + + // auto switching devs + if (post_init_timer && timer_elapsed32(post_init_timer) >= 100) { + + md_send_devctrl(MD_SND_CMD_DEVCTRL_FW_VERSION); // get the module fw version. + md_send_devctrl(MD_SND_CMD_DEVCTRL_SLEEP_BT_EN); // timeout 30min to sleep in bt mode, enable + md_send_devctrl(MD_SND_CMD_DEVCTRL_SLEEP_2G4_EN); // timeout 30min to sleep in 2.4g mode, enable + wireless_devs_change(!confinfo.devs, confinfo.devs, false); + post_init_timer = 0x00; + } + hs_mode_scan(false,confinfo.devs,confinfo.last_btdevs); +} + +uint32_t wls_process_long_press(uint32_t trigger_time, void *cb_arg) { + uint16_t keycode = *((uint16_t *)cb_arg); + + switch (keycode) { + case KC_BT1: { + uint8_t mode = confinfo.devs; + hs_modeio_detection(true, &mode,confinfo.last_btdevs); + if ((mode == hs_bt) || (mode == hs_wireless) || (mode == hs_none)) { + wireless_devs_change(wireless_get_current_devs(), DEVS_BT1, true); + } + } break; + case KC_BT2: { + uint8_t mode = confinfo.devs; + hs_modeio_detection(true, &mode,confinfo.last_btdevs); + if ((mode == hs_bt) || (mode == hs_wireless) || (mode == hs_none)) { + wireless_devs_change(wireless_get_current_devs(), DEVS_BT2, true); + } + } break; + case KC_BT3: { + uint8_t mode = confinfo.devs; + hs_modeio_detection(true, &mode,confinfo.last_btdevs); + if ((mode == hs_bt) || (mode == hs_wireless) || (mode == hs_none)) { + wireless_devs_change(wireless_get_current_devs(), DEVS_BT3, true); + } + } break; + case KC_2G4: { + uint8_t mode = confinfo.devs; + hs_modeio_detection(true, &mode,confinfo.last_btdevs); + if ((mode == hs_2g4) || (mode == hs_wireless) || (mode == hs_none)) { + wireless_devs_change(wireless_get_current_devs(), DEVS_2G4, true); + } + } break; + case EE_CLR: { + + } break; + default: + break; + } + + return 0; +} + +bool process_record_wls(uint16_t keycode, keyrecord_t *record) { + static uint16_t keycode_shadow = 0x00; + static deferred_token wls_process_long_press_token = INVALID_DEFERRED_TOKEN; + + keycode_shadow = keycode; + +# ifndef WLS_KEYCODE_PAIR_TIME +# define WLS_KEYCODE_PAIR_TIME 3000 +# endif + + # define WLS_KEYCODE_EXEC(wls_dev) \ + do { \ + if (record->event.pressed) { \ + if (wireless_get_current_devs() != wls_dev) \ + wireless_devs_change(wireless_get_current_devs(), wls_dev, false); \ + if (wls_process_long_press_token == INVALID_DEFERRED_TOKEN) { \ + wls_process_long_press_token = defer_exec(WLS_KEYCODE_PAIR_TIME, wls_process_long_press, &keycode_shadow); \ + } \ + } else { \ + cancel_deferred_exec(wls_process_long_press_token); \ + wls_process_long_press_token = INVALID_DEFERRED_TOKEN; \ + } \ + } while (false) + + switch (keycode) { + case KC_BT1: { + uint8_t mode = confinfo.devs; + hs_modeio_detection(true, &mode,confinfo.last_btdevs); + if ((mode == hs_bt) || (mode == hs_wireless) || (mode == hs_none)) { + WLS_KEYCODE_EXEC(DEVS_BT1); + hs_rgb_blink_set_timer(timer_read32()); + } + + } break; + case KC_BT2: { + uint8_t mode = confinfo.devs; + hs_modeio_detection(true, &mode,confinfo.last_btdevs); + if ((mode == hs_bt) || (mode == hs_wireless) || (mode == hs_none)) { + WLS_KEYCODE_EXEC(DEVS_BT2); + hs_rgb_blink_set_timer(timer_read32()); + } + } break; + case KC_BT3: { + uint8_t mode = confinfo.devs; + hs_modeio_detection(true, &mode,confinfo.last_btdevs); + if ((mode == hs_bt) || (mode == hs_wireless) || (mode == hs_none)) { + WLS_KEYCODE_EXEC(DEVS_BT3); + hs_rgb_blink_set_timer(timer_read32()); + } + } break; + case KC_2G4: { + uint8_t mode = confinfo.devs; + hs_modeio_detection(true, &mode,confinfo.last_btdevs); + if ((mode == hs_2g4) || (mode == hs_wireless) || (mode == hs_none)) { + WLS_KEYCODE_EXEC(DEVS_2G4); + hs_rgb_blink_set_timer(timer_read32()); + } + } break; + case KC_USB: { + if (record->event.pressed) { + wireless_devs_change(wireless_get_current_devs(), DEVS_USB, false); + } + } break; + default: + return true; + } + + return false; +} +#endif + +bool process_record_user(uint16_t keycode, keyrecord_t *record) { + + if (test_white_light_flag && record->event.pressed) { + test_white_light_flag = false; + rgb_matrix_set_color_all(0x00, 0x00, 0x00); + } + + if (*md_getp_state() == MD_STATE_CONNECTED){ + hs_rgb_blink_set_timer(timer_read32()); + } + + switch (keycode) { + case MO(_FL): + case MO(_MFL): { + if (!record->event.pressed && rgbrec_is_started()) { + if (no_record_fg == true) { + no_record_fg = false; + rgbrec_register_record(keycode, record); + } + no_record_fg = true; + } + break; + } + case RP_END: + case RP_P0: + case RP_P1: + case RP_P2: + case RGB_MOD: + break; + default: { + if (rgbrec_is_started()) { + if (!IS_QK_MOMENTARY(keycode) && record->event.pressed) { + rgbrec_register_record(keycode, record); + + return false; + } + } + } break; + } + + if (rgbrec_is_started() && (!(keycode == RP_P0 || keycode == RP_P1 || keycode == RP_P2 || keycode == RP_END || keycode == RGB_MOD || keycode == MO(_FL) || keycode == MO(_MFL)))) { + + return false; + } + + return true; +} + +void im_rgblight_increase(void) { + HSV rgb = rgblight_get_hsv(); + uint8_t mode = rgblight_get_mode(); + static uint8_t current_mode = 0; + + if (mode == 1) { + current_mode = (rgb.h == 0 && rgb.s != 0) ? 3 : 9; + + switch (rgb.h) { + case 40: current_mode = 4; break; + case 80: current_mode = 5; break; + case 120: current_mode = 6; break; + case 160: current_mode = 7; break; + case 200: current_mode = 8; break; + default: break; + } + } + + current_mode = (current_mode + 1) % 11; + + if (current_mode == 10) { + rgblight_sethsv(0, 255, rgb.v); + rgblight_disable(); + } else { + rgblight_enable(); + rgblight_mode(buff[current_mode]); + } + + switch (current_mode) { + case 3: rgblight_sethsv(0, 255, rgb.v); break; + case 4: rgblight_sethsv(40, 255, rgb.v); break; + case 5: rgblight_sethsv(80, 255, rgb.v); break; + case 6: rgblight_sethsv(120, 255, rgb.v); break; + case 7: rgblight_sethsv(160, 255, rgb.v); break; + case 8: rgblight_sethsv(200, 255, rgb.v); break; + case 9: rgblight_sethsv(0, 0, rgb.v); break; + case 0: rgblight_set_speed(255); break; + default: rgblight_set_speed(200); break; + } +} + + +bool process_record_kb(uint16_t keycode, keyrecord_t *record) { + + if (process_record_user(keycode, record) != true) { + return false; + } + +#ifdef WIRELESS_ENABLE + if (process_record_wls(keycode, record) != true) { + return false; + } +#endif + + switch (keycode) { + case KC_TILD:{ + if (record->event.pressed){ + register_code(KC_LSFT); + register_code(KC_GRV); + }else{ + unregister_code(KC_LSFT); + unregister_code(KC_GRV); + } + return false; + break; + } + case QK_BOOT: { + if (record->event.pressed) { + dprintf("into boot!!!\r\n"); + eeconfig_disable(); + bootloader_jump(); + } + } break; + case KC_TEST: { + if (rgbrec_is_started()) { + + return false; + } + if (record->event.pressed) { + test_white_light_flag = true; + } + return false; + } break; + case NK_TOGG: { + if (rgbrec_is_started()) { + + return false; + } + if (record->event.pressed) { + rgb_matrix_hs_indicator_set(0xFF, (RGB){0x00, 0x6E, 0x00}, 250, 1); + } + } break; + case RL_MOD: { + if (rgbrec_is_started()) { + + return false; + } + if (record->event.pressed) { + im_rgblight_increase(); + } + + return false; + } break; + case EE_CLR: { + if (record->event.pressed) { + ee_clr_timer = timer_read32(); + } else { + ee_clr_timer = 0; + } + + return false; + } break; + case RGB_SPI: { + if (record->event.pressed) { + if (rgb_matrix_get_speed() >= (RGB_MATRIX_SPD_STEP * 5)) { + rgb_blink_dir(); + } + } + } break; + case RGB_SPD: { + if (record->event.pressed) { + if (rgb_matrix_get_speed() <= RGB_MATRIX_SPD_STEP) { + rgb_blink_dir(); + rgb_matrix_set_speed(RGB_MATRIX_SPD_STEP); + + return false; + } + } + } break; + case RGB_VAI: { + if (record->event.pressed) { + if (rgb_matrix_get_val() >= (RGB_MATRIX_MAXIMUM_BRIGHTNESS - RGB_MATRIX_VAL_STEP)) { + rgb_blink_dir(); + start_hsv.v = RGB_MATRIX_MAXIMUM_BRIGHTNESS; + } + else{ + start_hsv.v = rgb_matrix_get_val() + RGB_MATRIX_VAL_STEP; + } + } + } break; + case RGB_VAD: { + if (record->event.pressed) { + if (rgb_matrix_get_val() <= RGB_MATRIX_VAL_STEP) { + rgb_blink_dir(); + for(uint8_t i = 0; i < RGB_MATRIX_LED_COUNT - RGBLED_NUM;i++){ + rgb_matrix_set_color(i,0,0,0); + } + start_hsv.v = 0; + } + else{ + start_hsv.v = rgb_matrix_get_val() - RGB_MATRIX_VAL_STEP; + } + } + } break; + case TO(_BL): { + if (record->event.pressed) { + rgb_matrix_hs_set_remain_time(HS_RGB_BLINK_INDEX_MAC, 0); + rgb_matrix_hs_indicator_set(HS_RGB_BLINK_INDEX_WIN, (RGB){RGB_WHITE}, 250, 3); + if (keymap_is_mac_system()) { + set_single_persistent_default_layer(_BL); + layer_move(0); + } + } + + return false; + } break; + case TO(_MBL): { + if (record->event.pressed) { + rgb_matrix_hs_set_remain_time(HS_RGB_BLINK_INDEX_WIN, 0); + rgb_matrix_hs_indicator_set(HS_RGB_BLINK_INDEX_MAC, (RGB){RGB_WHITE}, 250, 3); + if (!keymap_is_mac_system()) { + set_single_persistent_default_layer(_MBL); + layer_move(0); + } + } + + return false; + } break; + case RP_P0: { + if (record->event.pressed) { + confinfo.record_channel = 0; + rgbrec_read_current_channel(confinfo.record_channel); + rgbrec_end(confinfo.record_channel); + eeconfig_confinfo_update(confinfo.raw); + rgbrec_show(confinfo.record_channel); + dprintf("confinfo.record_last_mode = %d\r\n", confinfo.record_last_mode); + } + + return false; + } break; + case RP_P1: { + if (record->event.pressed) { + confinfo.record_channel = 1; + rgbrec_read_current_channel(confinfo.record_channel); + rgbrec_end(confinfo.record_channel); + eeconfig_confinfo_update(confinfo.raw); + rgbrec_show(confinfo.record_channel); + } + + return false; + } break; + case RP_P2: { + if (record->event.pressed) { + confinfo.record_channel = 2; + rgbrec_read_current_channel(confinfo.record_channel); + rgbrec_end(confinfo.record_channel); + eeconfig_confinfo_update(confinfo.raw); + rgbrec_show(confinfo.record_channel); + } + + return false; + } break; + case RP_END: { + if (record->event.pressed) { + if (rgb_matrix_get_mode() != RGB_MATRIX_CUSTOM_RGBR_PLAY) { + + return false; + } + if (!rgbrec_is_started()) { + rgbrec_start(confinfo.record_channel); + no_record_fg = false; + rec_time = timer_read32(); + rgbrec_set_close_all(HSV_BLACK); + } else { + rec_time = 0; + rgbrec_end(confinfo.record_channel); + } + dprintf("confinfo.record_last_mode = %d\r\n", confinfo.record_last_mode); + } + + return false; + } break; + case RGB_MOD: { + if (record->event.pressed) { + if (rgb_matrix_get_mode() == RGB_MATRIX_CUSTOM_RGBR_PLAY) { + if (rgbrec_is_started()) { + rgbrec_read_current_channel(confinfo.record_channel); + rgbrec_end(confinfo.record_channel); + no_record_fg = false; + } + if (confinfo.record_last_mode != 0xFF) + rgb_matrix_mode(confinfo.record_last_mode); + else + rgb_matrix_mode(RGB_MATRIX_DEFAULT_MODE); + eeconfig_confinfo_update(confinfo.raw); + dprintf("confinfo.record_last_mode = %d\r\n", confinfo.record_last_mode); + start_hsv = rgb_matrix_get_hsv(); + return false; + } + record_rgbmatrix_increase(&(confinfo.record_last_mode)); + eeconfig_confinfo_update(confinfo.raw); + start_hsv = rgb_matrix_get_hsv(); + } + + return false; + } break; + case RGB_HUI: { + if (record->event.pressed) { + record_color_hsv(true); + start_hsv = rgb_matrix_get_hsv(); + } + + return false; + } break; + case KC_LCMD: { + if (keymap_is_mac_system()) { + if (keymap_config.no_gui && !rgbrec_is_started()) { + if (record->event.pressed) { + register_code16(KC_LCMD); + } else { + unregister_code16(KC_LCMD); + } + } + } + + return true; + } break; + case KC_RCMD: { + if (keymap_is_mac_system()) { + if (keymap_config.no_gui && !rgbrec_is_started()) { + if (record->event.pressed) { + register_code16(KC_RCMD); + } else { + unregister_code16(KC_RCMD); + } + } + } + + return true; + } break; + case HS_BATQ: { + extern bool rk_bat_req_flag; + rk_bat_req_flag = (confinfo.devs != DEVS_USB) && record->event.pressed; + return false; + } break; + default: + break; + } + + return true; +} + +void housekeeping_task_user(void) { + uint8_t hs_now_mode; + static uint32_t hs_current_time; + static bool val_value = false; + + charging_state = readPin(HS_BAT_CABLE_PIN); + + bat_full_flag = readPin(BAT_FULL_PIN); + + + if (charging_state && (bat_full_flag)) { + hs_now_mode = MD_SND_CMD_DEVCTRL_CHARGING_DONE; + } else if (charging_state) { + hs_now_mode = MD_SND_CMD_DEVCTRL_CHARGING; + } else { + hs_now_mode = MD_SND_CMD_DEVCTRL_CHARGING_STOP; + } + + if (!hs_current_time || timer_elapsed32(hs_current_time) > 1000) { + + hs_current_time = timer_read32(); + md_send_devctrl(hs_now_mode); + md_send_devctrl(MD_SND_CMD_DEVCTRL_INQVOL); + } + + if (charging_state){ +#ifdef HS_LED_BOOSTING_PIN + writePin(HS_LED_BOOSTING_PIN,0); +#endif + if(!val_value){ + rgb_matrix_sethsv_noeeprom(start_hsv.h,start_hsv.s,150); + } + val_value = true; + + } + else{ +#ifdef HS_LED_BOOSTING_PIN + writePin(HS_LED_BOOSTING_PIN,1); +#endif + if(val_value){ + rgb_matrix_sethsv(start_hsv.h,start_hsv.s,start_hsv.v); + } + val_value = false; + } + +} + +#ifdef RGB_MATRIX_ENABLE + +# ifdef WIRELESS_ENABLE +bool wls_rgb_indicator_reset = false; +uint32_t wls_rgb_indicator_timer = 0x00; +uint32_t wls_rgb_indicator_interval = 0; +uint32_t wls_rgb_indicator_times = 0; +uint32_t wls_rgb_indicator_index = 0; +RGB wls_rgb_indicator_rgb = {0}; + +void rgb_matrix_wls_indicator_set(uint8_t index, RGB rgb, uint32_t interval, uint8_t times) { + + wls_rgb_indicator_timer = timer_read32(); + + wls_rgb_indicator_index = index; + wls_rgb_indicator_interval = interval; + wls_rgb_indicator_times = times * 2; + wls_rgb_indicator_rgb = rgb; +} + +void wireless_devs_change_kb(uint8_t old_devs, uint8_t new_devs, bool reset) { + + wls_rgb_indicator_reset = reset; + + if (confinfo.devs != wireless_get_current_devs()) { + confinfo.devs = wireless_get_current_devs(); + if (confinfo.devs > 0 && confinfo.devs < 4) confinfo.last_btdevs = confinfo.devs; + eeconfig_confinfo_update(confinfo.raw); + } + + switch (new_devs) { + case DEVS_BT1: { + if (reset) { + rgb_matrix_wls_indicator_set(HS_RGB_BLINK_INDEX_BT1, (RGB){HS_LBACK_COLOR_BT1}, 200, 1); + } else { + rgb_matrix_wls_indicator_set(HS_RGB_BLINK_INDEX_BT1, (RGB){HS_PAIR_COLOR_BT1}, 500, 1); + } + } break; + case DEVS_BT2: { + if (reset) { + rgb_matrix_wls_indicator_set(HS_RGB_BLINK_INDEX_BT2, (RGB){HS_LBACK_COLOR_BT2}, 200, 1); + } else { + rgb_matrix_wls_indicator_set(HS_RGB_BLINK_INDEX_BT2, (RGB){HS_PAIR_COLOR_BT2}, 500, 1); + } + } break; + case DEVS_BT3: { + if (reset) { + rgb_matrix_wls_indicator_set(HS_RGB_BLINK_INDEX_BT3, (RGB){HS_LBACK_COLOR_BT3}, 200, 1); + } else { + rgb_matrix_wls_indicator_set(HS_RGB_BLINK_INDEX_BT3, (RGB){HS_PAIR_COLOR_BT3}, 500, 1); + } + } break; + case DEVS_2G4: { + if (reset) { + rgb_matrix_wls_indicator_set(HS_RGB_BLINK_INDEX_2G4, (RGB){HS_LBACK_COLOR_2G4}, 200, 1); + } else { + rgb_matrix_wls_indicator_set(HS_RGB_BLINK_INDEX_2G4, (RGB){HS_LBACK_COLOR_2G4}, 500, 1); + } + } break; + default: + break; + } +} + +bool rgb_matrix_wls_indicator_cb(void) { + + if (*md_getp_state() != MD_STATE_CONNECTED) { + wireless_devs_change_kb(wireless_get_current_devs(), wireless_get_current_devs(), wls_rgb_indicator_reset); + return true; + } + + // refresh led + led_wakeup(); + + return false; +} + +void rgb_matrix_wls_indicator(void) { + + if (wls_rgb_indicator_timer) { + + if (timer_elapsed32(wls_rgb_indicator_timer) >= wls_rgb_indicator_interval) { + wls_rgb_indicator_timer = timer_read32(); + + if (wls_rgb_indicator_times) { + wls_rgb_indicator_times--; + } + + if (wls_rgb_indicator_times <= 0) { + wls_rgb_indicator_timer = 0x00; + if (rgb_matrix_wls_indicator_cb() != true) { + return; + } + } + } + + if (wls_rgb_indicator_times % 2) { + rgb_matrix_set_color(wls_rgb_indicator_index, wls_rgb_indicator_rgb.r, wls_rgb_indicator_rgb.g, wls_rgb_indicator_rgb.b); + } else { + rgb_matrix_set_color(wls_rgb_indicator_index, 0x00, 0x00, 0x00); + } + } +} + +void rgb_matrix_hs_bat_set(uint8_t index, RGB rgb, uint32_t interval, uint8_t times) { + for (int i = 0; i < HS_RGB_BAT_COUNT; i++) { + if (!hs_rgb_bat[i].active) { + hs_rgb_bat[i].active = true; + hs_rgb_bat[i].timer = timer_read32(); + hs_rgb_bat[i].interval = interval; + hs_rgb_bat[i].times = times * 2; + hs_rgb_bat[i].index = index; + hs_rgb_bat[i].rgb = rgb; + break; + } + } +} + +void rgb_matrix_hs_bat(void) { + for (int i = 0; i < HS_RGB_BAT_COUNT; i++) { + if (hs_rgb_bat[i].active) { + if (timer_elapsed32(hs_rgb_bat[i].timer) >= hs_rgb_bat[i].interval) { + hs_rgb_bat[i].timer = timer_read32(); + + if (hs_rgb_bat[i].times) { + hs_rgb_bat[i].times--; + } + + if (hs_rgb_bat[i].times <= 0) { + hs_rgb_bat[i].active = false; + hs_rgb_bat[i].timer = 0x00; + } + } + + if (hs_rgb_bat[i].times % 2) { + rgb_matrix_set_color(hs_rgb_bat[i].index, hs_rgb_bat[i].rgb.r, hs_rgb_bat[i].rgb.g, hs_rgb_bat[i].rgb.b); + } else { + rgb_matrix_set_color(hs_rgb_bat[i].index, 0x00, 0x00, 0x00); + } + } + } +} + +void bat_indicators(void) { + static uint32_t battery_process_time = 0; + + if (charging_state && (bat_full_flag)) { + + battery_process_time = 0; + } else if (charging_state) { + + battery_process_time = 0; + rgb_matrix_set_color(HS_MATRIX_BLINK_INDEX_BAT, RGB_RED); + } else if (*md_getp_bat() <= BATTERY_CAPACITY_LOW) { + + rgb_matrix_hs_bat_set(HS_MATRIX_BLINK_INDEX_BAT, (RGB){RGB_RED}, 250, 1); + + if (*md_getp_bat() <= BATTERY_CAPACITY_STOP) { + if (!battery_process_time) { + battery_process_time = timer_read32(); + } + + if (battery_process_time && timer_elapsed32(battery_process_time) > 20000) { + battery_process_time = 0; + lower_sleep = true; + lpwr_set_timeout_manual(false); + } + } + } else { + battery_process_time = 0; + } +} + +# endif + +#endif + +void rgb_blink_dir(void) { + + rgb_matrix_hs_indicator_set(HS_RGB_BLINK_INDEX_VAI, (RGB){WIITE_B, WIITE_B, WIITE_B}, 250, 3); + rgb_matrix_hs_indicator_set(HS_RGB_BLINK_INDEX_VAD, (RGB){WIITE_B, WIITE_B, WIITE_B}, 250, 3); + rgb_matrix_hs_indicator_set(HS_RGB_BLINK_INDEX_SPI, (RGB){WIITE_B, WIITE_B, WIITE_B}, 250, 3); + rgb_matrix_hs_indicator_set(HS_RGB_BLINK_INDEX_SPD, (RGB){WIITE_B, WIITE_B, WIITE_B}, 250, 3); +} + +bool hs_reset_settings_user(void) { + + rgb_matrix_hs_indicator_set(0xFF, (RGB){WIITE_B,WIITE_B,WIITE_B}, 250, 3); + + return true; +} + +void nkr_indicators_hook(uint8_t index) { + + if ((hs_rgb_indicators[index].rgb.r == WIITE_B) && (hs_rgb_indicators[index].rgb.g == 0x00) && (hs_rgb_indicators[index].rgb.b == 0x00)) { + + rgb_matrix_hs_indicator_set(0xFF, (RGB){WIITE_B, 0x00, 0x00}, 250, 1); + + } else if ((hs_rgb_indicators[index].rgb.r == 0x00) && (hs_rgb_indicators[index].rgb.g == 0x6E) && (hs_rgb_indicators[index].rgb.b == 0x00)) { + + rgb_matrix_hs_indicator_set(0xFF, (RGB){0x00, 0x00, WIITE_B}, 250, 1); + } +} + +void rgb_matrix_hs_indicator_set(uint8_t index, RGB rgb, uint32_t interval, uint8_t times) { + + for (int i = 0; i < HS_RGB_INDICATOR_COUNT; i++) { + if (!hs_rgb_indicators[i].active) { + hs_rgb_indicators[i].active = true; + hs_rgb_indicators[i].timer = timer_read32(); + hs_rgb_indicators[i].interval = interval; + hs_rgb_indicators[i].times = times * 2; + hs_rgb_indicators[i].index = index; + hs_rgb_indicators[i].rgb = rgb; + if (index != 0xFF) + hs_rgb_indicators[i].blink_cb = NULL; + else { + hs_rgb_indicators[i].blink_cb = nkr_indicators_hook; + } + break; + } + } +} + +void rgb_matrix_hs_set_remain_time(uint8_t index, uint8_t remain_time) { + + for (int i = 0; i < HS_RGB_INDICATOR_COUNT; i++) { + if (hs_rgb_indicators[i].index == index) { + hs_rgb_indicators[i].times = 0; + hs_rgb_indicators[i].active = false; + break; + } + } +} + +void rgb_matrix_hs_indicator(void) { + + for (int i = 0; i < HS_RGB_INDICATOR_COUNT; i++) { + if (hs_rgb_indicators[i].active) { + if (timer_elapsed32(hs_rgb_indicators[i].timer) >= hs_rgb_indicators[i].interval) { + hs_rgb_indicators[i].timer = timer_read32(); + + if (hs_rgb_indicators[i].times) { + hs_rgb_indicators[i].times--; + } + + if (hs_rgb_indicators[i].times <= 0) { + hs_rgb_indicators[i].active = false; + hs_rgb_indicators[i].timer = 0x00; + if (hs_rgb_indicators[i].blink_cb != NULL) + hs_rgb_indicators[i].blink_cb(i); + continue; + } + } + + if (!(hs_rgb_indicators[i].times % 2)) { + if (hs_rgb_indicators[i].index == 0xFF) { + rgb_matrix_set_color_all(hs_rgb_indicators[i].rgb.r, hs_rgb_indicators[i].rgb.g, hs_rgb_indicators[i].rgb.b); + } else { + rgb_matrix_set_color(hs_rgb_indicators[i].index, hs_rgb_indicators[i].rgb.r, hs_rgb_indicators[i].rgb.g, hs_rgb_indicators[i].rgb.b); + } + } else { + if (hs_rgb_indicators[i].index == 0xFF) { + rgb_matrix_set_color_all(0x00, 0x00, 0x00); + } else { + rgb_matrix_set_color(hs_rgb_indicators[i].index, 0x00, 0x00, 0x00); + } + } + } + } +} + +void rgb_matrix_start_rec(void) { + + if (rgbrec_is_started()) { + if (!rec_time || timer_elapsed32(rec_time) > 250) { + rec_time = timer_read32(); + rec_filp = !rec_filp; + } + if (rec_filp) { + rgb_matrix_set_color(0, WIITE_B, WIITE_B, WIITE_B); + rgb_matrix_set_color(1, WIITE_B, WIITE_B, WIITE_B); + rgb_matrix_set_color(2, WIITE_B, WIITE_B, WIITE_B); + } else { + rgb_matrix_set_color(0, 0x00, 0x00, 0x00); + rgb_matrix_set_color(1, 0x00, 0x00, 0x00); + rgb_matrix_set_color(2, 0x00, 0x00, 0x00); + } + } else { + rec_time = 0; + rec_filp = false; + } +} + +bool rgb_matrix_indicators_advanced_kb(uint8_t led_min, uint8_t led_max) { + extern RGB rgb_matrix_ws2812_array[HS_RGB_INDICATOR_COUNT]; + + uint8_t r,g,b; + r = rgb_matrix_ws2812_array[81].r; + g = rgb_matrix_ws2812_array[81].g; + b = rgb_matrix_ws2812_array[81].b; + rgb_matrix_set_color(82,r,g,b); + + if (test_white_light_flag) { + RGB rgb_test_open = hsv_to_rgb((HSV){.h = 0, .s = 0, .v = RGB_MATRIX_VAL_STEP * 5}); + rgb_matrix_set_color_all(rgb_test_open.r, rgb_test_open.g, rgb_test_open.b); + + return false; + } + + if (rgb_matrix_indicators_advanced_user(led_min, led_max) != true) { + + return false; + } + + if (ee_clr_timer && timer_elapsed32(ee_clr_timer) > 3000) { + hs_reset_settings(); + ee_clr_timer = 0; + } + + if ((*md_getp_state() == MD_STATE_CONNECTED) || USB_DRIVER.state == USB_ACTIVE) + { + if (host_keyboard_led_state().caps_lock) + rgb_matrix_set_color(HS_RGB_INDEX_CAPS, RGB_WHITE); + + if (!keymap_is_mac_system() && keymap_config.no_gui) + rgb_matrix_set_color(HS_RGB_INDEX_WIN_LOCK, RGB_WHITE); + } + +#ifdef RGBLIGHT_ENABLE + if (rgb_matrix_indicators_advanced_rgblight(led_min, led_max) != true) { + + return false; + } +#endif + + # ifdef WIRELESS_ENABLE + rgb_matrix_wls_indicator(); + + if (enable_bat_indicators && !inqbat_flag && !rgbrec_is_started()) { + rgb_matrix_hs_bat(); + bat_indicators(); + bat_indicator_cnt = timer_read32(); + } + + if (!enable_bat_indicators) { + if (timer_elapsed32(bat_indicator_cnt) > 2000){ + enable_bat_indicators = true; + bat_indicator_cnt = timer_read32(); + } + } + + if (confinfo.devs == DEVS_USB){ + if (USB_DRIVER.state != USB_ACTIVE) { + if(enable_bat_indicators){ + rgb_matrix_hs_indicator_set(HS_RGB_BLINK_INDEX_USB, (RGB){HS_LBACK_COLOR_USB}, 500, 1); + } + } + } + +# endif + + rgb_matrix_hs_indicator(); + + query(); + return true; +} + +void hs_reset_settings(void) { + enable_bat_indicators = false; + eeconfig_init(); + +#ifdef RGBLIGHT_ENABLE + extern void rgblight_init(void); + is_rgblight_initialized = false; + rgblight_init(); + eeconfig_update_rgblight_default(); + rgblight_enable(); +#endif + + eeconfig_update_rgb_matrix_default(); + keymap_config.raw = eeconfig_read_keymap(); + +#if defined(NKRO_ENABLE) && defined(FORCE_NKRO) + keymap_config.nkro = 0; + eeconfig_update_keymap(keymap_config.raw); +#endif + +#if defined(WIRELESS_ENABLE) + wireless_devs_change(wireless_get_current_devs(), DEVS_USB, false); +#endif + + if (hs_reset_settings_user() != true) { + + return; + } + hs_rgb_blink_set_timer(timer_read32()); + keyboard_post_init_kb(); +} + +