feat: expose persistent effect lifecycle, wakeup, fade-delay, idle hooks

Core API additions for full parity with darknao fork behaviors:

- ZMK_RGB_EFFECT_PERSISTENT flag: effect persists across RGB on/off
- on_idle(bool awake) callback: effect controls sleep/wake behavior
- is_active() callback: get_state returns on || effect->is_active()
- zmk_rgb_request_refresh_wakeup(bool): conditional wake from sleep
- zmk_rgb_set_tick_delay(int): delayed tick restart for fade-delay
- zmk_rgb_is_on(): exposed state query for modules
- state.layer_enabled restored: persistent effects survive RGB toggle
- auto_state delegates to on_idle for effects that implement it
- select_effect sets layer_enabled for PERSISTENT effects
- transient_off no longer calls on_deselect (only true deselect does)
This commit is contained in:
afiqzudinhadi 2026-07-01 15:16:52 +08:00
parent 8f3c4d3929
commit a36c7f26f8
2 changed files with 86 additions and 14 deletions

View file

@ -19,28 +19,39 @@ struct zmk_rgb_effect_ctx {
typedef void (*zmk_rgb_effect_render_fn)(struct zmk_rgb_effect_ctx *ctx);
typedef void (*zmk_rgb_effect_event_fn)(void);
typedef void (*zmk_rgb_effect_idle_fn)(bool awake);
typedef bool (*zmk_rgb_effect_is_active_fn)(void);
#define ZMK_RGB_EFFECT_STATIC BIT(0)
#define ZMK_RGB_EFFECT_PERSISTENT BIT(1)
struct zmk_rgb_effect {
const char *name;
zmk_rgb_effect_render_fn render;
zmk_rgb_effect_event_fn on_select;
zmk_rgb_effect_event_fn on_deselect;
zmk_rgb_effect_idle_fn on_idle;
zmk_rgb_effect_is_active_fn is_active;
uint8_t flags;
};
#define ZMK_RGB_EFFECT_DEFINE(_sym, _name_str, _render, _flags, _on_select, _on_deselect) \
#define ZMK_RGB_EFFECT_DEFINE(_sym, _name_str, _render, _flags, _on_select, _on_deselect, \
_on_idle, _is_active) \
STRUCT_SECTION_ITERABLE(zmk_rgb_effect, _sym) = { \
.name = _name_str, \
.render = _render, \
.flags = _flags, \
.on_select = _on_select, \
.on_deselect = _on_deselect, \
.on_idle = _on_idle, \
.is_active = _is_active, \
}
void zmk_rgb_request_refresh(void);
void zmk_rgb_request_refresh_wakeup(bool wakeup);
int zmk_rgb_effect_get_count(void);
bool zmk_rgb_is_on(void);
void zmk_rgb_set_tick_delay(int delay_seconds);
struct led_rgb zmk_rgb_hsb_to_rgb(struct zmk_led_hsb hsb);
struct zmk_led_hsb zmk_rgb_hsb_scale_min_max(struct zmk_led_hsb hsb);

View file

@ -67,6 +67,7 @@ struct rgb_underglow_state {
uint16_t animation_step;
bool on;
bool status_active;
bool layer_enabled;
uint16_t status_animation_step;
};
@ -192,10 +193,11 @@ static void effect_swirl_render(struct zmk_rgb_effect_ctx *ctx) {
*ctx->animation_step = *ctx->animation_step % HUE_MAX;
}
ZMK_RGB_EFFECT_DEFINE(effect_solid, "Solid", effect_solid_render, 0, NULL, NULL);
ZMK_RGB_EFFECT_DEFINE(effect_breathe, "Breathe", effect_breathe_render, 0, NULL, NULL);
ZMK_RGB_EFFECT_DEFINE(effect_spectrum, "Spectrum", effect_spectrum_render, 0, NULL, NULL);
ZMK_RGB_EFFECT_DEFINE(effect_swirl, "Swirl", effect_swirl_render, 0, NULL, NULL);
ZMK_RGB_EFFECT_DEFINE(effect_solid, "Solid", effect_solid_render, 0, NULL, NULL, NULL, NULL);
ZMK_RGB_EFFECT_DEFINE(effect_breathe, "Breathe", effect_breathe_render, 0, NULL, NULL, NULL, NULL);
ZMK_RGB_EFFECT_DEFINE(effect_spectrum, "Spectrum", effect_spectrum_render, 0, NULL, NULL, NULL,
NULL);
ZMK_RGB_EFFECT_DEFINE(effect_swirl, "Swirl", effect_swirl_render, 0, NULL, NULL, NULL, NULL);
/* --- Effect registry helpers --- */
@ -215,6 +217,11 @@ static struct zmk_rgb_effect *zmk_rgb_effect_get(int index) {
return effect;
}
static bool zmk_rgb_effect_is_persistent(void) {
struct zmk_rgb_effect *effect = zmk_rgb_effect_get(state.current_effect);
return effect && (effect->flags & ZMK_RGB_EFFECT_PERSISTENT);
}
/* --- Status indicator overlay (separate from effects) --- */
static int zmk_led_generate_status(void);
@ -462,7 +469,7 @@ static void zmk_rgb_underglow_tick(struct k_work *work) {
K_WORK_DEFINE(underglow_tick_work, zmk_rgb_underglow_tick);
static void zmk_rgb_underglow_tick_handler(struct k_timer *timer) {
if (!state.on) {
if (!state.on && !state.layer_enabled) {
return;
}
@ -471,10 +478,30 @@ static void zmk_rgb_underglow_tick_handler(struct k_timer *timer) {
K_TIMER_DEFINE(underglow_tick, zmk_rgb_underglow_tick_handler, NULL);
bool zmk_rgb_is_on(void) { return state.on; }
void zmk_rgb_request_refresh(void) {
k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &underglow_tick_work);
}
void zmk_rgb_request_refresh_wakeup(bool wakeup) {
if (!state.on && !state.layer_enabled) {
if (!wakeup) {
return;
}
zmk_rgb_underglow_transient_on();
}
zmk_rgb_request_refresh();
}
void zmk_rgb_set_tick_delay(int delay_seconds) {
k_timer_stop(&underglow_tick);
state.animation_step = 0;
if (delay_seconds >= 0) {
k_timer_start(&underglow_tick, K_SECONDS(delay_seconds), K_MSEC(50));
}
}
/* --- Settings persistence --- */
#if IS_ENABLED(CONFIG_SETTINGS)
@ -495,6 +522,12 @@ static int rgb_settings_set(const char *name, size_t len, settings_read_cb read_
if (state.on) {
k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50));
}
if (state.layer_enabled) {
struct zmk_rgb_effect *effect = zmk_rgb_effect_get(state.current_effect);
if (effect && effect->on_select) {
effect->on_select();
}
}
return 0;
}
@ -555,6 +588,12 @@ static int zmk_rgb_underglow_init(void) {
k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(25));
}
}
if (state.layer_enabled) {
struct zmk_rgb_effect *effect = zmk_rgb_effect_get(state.current_effect);
if (effect && effect->on_select) {
effect->on_select();
}
}
return 0;
}
@ -572,7 +611,7 @@ int zmk_rgb_underglow_get_state(bool *on_off) {
if (!led_strip)
return -ENODEV;
*on_off = state.on;
*on_off = state.on || state.layer_enabled;
return 0;
}
@ -611,6 +650,10 @@ void zmk_rgb_set_ext_power(void) {
int zmk_rgb_underglow_on(void) {
zmk_rgb_underglow_transient_on();
if (zmk_rgb_effect_is_persistent()) {
state.layer_enabled = true;
memset(pixels, 0, sizeof(struct led_rgb) * STRIP_NUM_PIXELS);
}
return zmk_rgb_underglow_save_state();
}
@ -647,6 +690,7 @@ K_WORK_DEFINE(underglow_off_work, zmk_rgb_underglow_off_handler);
int zmk_rgb_underglow_off(void) {
zmk_rgb_underglow_transient_off();
state.layer_enabled = false;
return zmk_rgb_underglow_save_state();
}
@ -654,11 +698,6 @@ int zmk_rgb_underglow_transient_off(void) {
if (!led_strip)
return -ENODEV;
struct zmk_rgb_effect *effect = zmk_rgb_effect_get(state.current_effect);
if (effect && effect->on_deselect) {
effect->on_deselect();
}
k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &underglow_off_work);
k_timer_stop(&underglow_tick);
@ -684,6 +723,8 @@ int zmk_rgb_underglow_select_effect(int effect) {
return -EINVAL;
}
bool was_persistent = zmk_rgb_effect_is_persistent();
struct zmk_rgb_effect *old_eff = zmk_rgb_effect_get(state.current_effect);
if (old_eff && old_eff->on_deselect) {
old_eff->on_deselect();
@ -693,16 +734,26 @@ int zmk_rgb_underglow_select_effect(int effect) {
state.animation_step = 0;
struct zmk_rgb_effect *new_eff = zmk_rgb_effect_get(state.current_effect);
state.layer_enabled = (new_eff && (new_eff->flags & ZMK_RGB_EFFECT_PERSISTENT));
if (new_eff && new_eff->on_select) {
new_eff->on_select();
}
if (state.on) {
if (new_eff && (new_eff->flags & ZMK_RGB_EFFECT_STATIC)) {
if (state.layer_enabled) {
memset(pixels, 0, sizeof(struct led_rgb) * STRIP_NUM_PIXELS);
zmk_rgb_request_refresh();
} else if (new_eff && (new_eff->flags & ZMK_RGB_EFFECT_STATIC)) {
k_timer_stop(&underglow_tick);
zmk_rgb_request_refresh();
} else {
if (was_persistent && !state.layer_enabled) {
k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(25));
} else {
k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(25));
}
}
}
@ -870,6 +921,11 @@ static int rgb_underglow_auto_state(bool target_wake_state) {
sleep_state.is_awake = target_wake_state;
if (sleep_state.is_awake) {
struct zmk_rgb_effect *effect = zmk_rgb_effect_get(state.current_effect);
if (effect && effect->on_idle) {
effect->on_idle(true);
return 0;
}
if (sleep_state.rgb_state_before_sleeping) {
return zmk_rgb_underglow_transient_on();
} else {
@ -877,6 +933,11 @@ static int rgb_underglow_auto_state(bool target_wake_state) {
}
} else {
sleep_state.rgb_state_before_sleeping = state.on;
struct zmk_rgb_effect *effect = zmk_rgb_effect_get(state.current_effect);
if (effect && effect->on_idle) {
effect->on_idle(false);
return 0;
}
return zmk_rgb_underglow_transient_off();
}
}