This commit is contained in:
Francois Andrieu 2026-01-30 15:46:26 +04:00 committed by GitHub
commit 2f3869ff0f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 928 additions and 15 deletions

View File

@ -89,6 +89,12 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
endif()
target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_rgb_underglow.c)
target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_underglow_color.c)
if (CONFIG_ZMK_HID_INDICATORS)
target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_underglow_indicators.c)
endif()
target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_underglow_battery.c)
target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/events/underglow_color_changed.c)
target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/behaviors/behavior_backlight.c)
target_sources_ifdef(CONFIG_ZMK_BATTERY_REPORTING app PRIVATE src/events/battery_state_changed.c)
@ -97,11 +103,13 @@ target_sources_ifdef(CONFIG_ZMK_BATTERY_REPORTING app PRIVATE src/battery.c)
target_sources_ifdef(CONFIG_ZMK_HID_INDICATORS app PRIVATE src/events/hid_indicators_changed.c)
target_sources_ifdef(CONFIG_ZMK_SPLIT app PRIVATE src/events/split_peripheral_status_changed.c)
target_sources_ifdef(CONFIG_ZMK_SPLIT app PRIVATE src/events/split_peripheral_layer_changed.c)
add_subdirectory_ifdef(CONFIG_ZMK_SPLIT src/split)
target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/usb.c)
target_sources_ifdef(CONFIG_ZMK_USB app PRIVATE src/usb_hid.c)
target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/rgb_underglow.c)
target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/rgb_underglow_layer.c)
target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/backlight.c)
target_sources_ifdef(CONFIG_ZMK_LOW_PRIORITY_WORK_QUEUE app PRIVATE src/workqueue.c)
target_sources(app PRIVATE src/main.c)

View File

@ -330,6 +330,10 @@ config ZMK_RGB_UNDERGLOW_AUTO_OFF_USB
bool "Turn off RGB underglow when USB is disconnected"
depends on USB_DEVICE_STACK
config EXPERIMENTAL_RGB_LAYER
bool "Experimental per-key per-layer RGB underglow"
default n
endif # ZMK_RGB_UNDERGLOW
menuconfig ZMK_BACKLIGHT

View File

@ -19,6 +19,15 @@
zmk,battery = &vbatt;
};
underglow-layer {
compatible = "zmk,underglow-layer";
pixel-lookup =
<52>, <53>, <54>, <69>, <70>, <71>, <15>, <27>, <39>, <51>, <4>, <14>, <26>, <38>,
<50>, <68>, <3>, <13>, <25>, <37>, <49>, <67>, <2>, <12>, <24>, <36>, <48>, <66>,
<1>, <11>, <23>, <35>, <47>, <65>, <0>, <10>, <22>, <34>, <46>, <64>;
};
back_led_backlight: pwmleds {
compatible = "pwm-leds";
pwm_led_0 {

View File

@ -75,6 +75,13 @@ CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB=y
# space.
CONFIG_ZMK_HID_CONSUMER_REPORT_USAGES_BASIC=y
# Enable USB boot protocol support
CONFIG_ZMK_USB_BOOT=y
CONFIG_ZMK_HID_INDICATORS=y
# Send HID indicator to peripherals
CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS=y
# Turn on debugging to disable optimization. Debug messages can result in larger
# stacks, so enable stack protection and particularly a larger BLE peripheral stack.
# CONFIG_DEBUG=y

View File

@ -20,6 +20,15 @@
zmk,battery = &vbatt;
};
underglow-layer {
compatible = "zmk,underglow-layer";
pixel-lookup =
<57>, <56>, <55>, <74>, <73>, <72>, <16>, <28>, <40>, <58>, <5>, <17>, <29>, <41>,
<59>, <75>, <6>, <18>, <30>, <42>, <60>, <76>, <7>, <19>, <31>, <43>, <61>, <77>,
<8>, <20>, <32>, <44>, <62>, <78>, <9>, <21>, <33>, <45>, <63>, <79>;
};
back_led_backlight: pwmleds {
compatible = "pwm-leds";
pwm_led_0 {

View File

@ -59,6 +59,10 @@ CONFIG_ZMK_RGB_UNDERGLOW_HUE_START=285
CONFIG_ZMK_RGB_UNDERGLOW_SAT_START=75
CONFIG_ZMK_RGB_UNDERGLOW_BRT_START=16
# Enable HID indicators on peripheral
CONFIG_ZMK_HID_INDICATORS=y
CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS=y
# The power LED is implemented as a backlight
# For now, the power LED is acting as a "USB connected" indicator
CONFIG_ZMK_BACKLIGHT=y

View File

@ -28,3 +28,6 @@
#include <behaviors/soft_off.dtsi>
#include <behaviors/studio_unlock.dtsi>
#include <behaviors/mouse_keys.dtsi>
#include <behaviors/ug_color.dtsi>
#include <behaviors/ug_indicators.dtsi>
#include <behaviors/ug_battery.dtsi>

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
/ {
behaviors {
ug_b2: ugbat20 {
compatible = "zmk,behavior-underglow-battery";
threshold = <20>;
#binding-cells = <2>;
display-name = "Underglow Battery level 20%";
};
ug_b4: ugbat40 {
compatible = "zmk,behavior-underglow-battery";
threshold = <40>;
#binding-cells = <2>;
display-name = "Underglow Battery level 40%";
};
ug_b6: ugbat60 {
compatible = "zmk,behavior-underglow-battery";
threshold = <60>;
#binding-cells = <2>;
display-name = "Underglow Battery level 60%";
};
ug_b8: ugbat80 {
compatible = "zmk,behavior-underglow-battery";
threshold = <80>;
#binding-cells = <2>;
display-name = "Underglow Battery level 80%";
};
};
};

View File

@ -0,0 +1,15 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
/ {
behaviors {
ug: ugcolor {
compatible = "zmk,behavior-underglow-color";
#binding-cells = <1>;
display-name = "Underglow Color";
};
};
};

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <dt-bindings/zmk/hid_indicators.h>
/ {
behaviors {
ug_nl: ugnumlk {
compatible = "zmk,behavior-underglow-indicators";
indicator = <NUM_LOCK>;
#binding-cells = <2>;
display-name = "Underglow NumLock indicator";
};
ug_cl: ugcapslk {
compatible = "zmk,behavior-underglow-indicators";
indicator = <CAPS_LOCK>;
#binding-cells = <2>;
display-name = "Underglow CapsLock indicator";
};
ug_sl: ugscrllk {
compatible = "zmk,behavior-underglow-indicators";
indicator = <SCROLL_LOCK>;
#binding-cells = <2>;
display-name = "Underglow ScrollLock indicator";
};
};
};

View File

@ -0,0 +1,12 @@
# Copyright (c) 2024, The ZMK Contributors
# SPDX-License-Identifier: MIT
description: Set underglow color based on battery level
compatible: "zmk,behavior-underglow-battery"
include: two_param.yaml
properties:
threshold:
type: int

View File

@ -0,0 +1,8 @@
# Copyright (c) 2024, The ZMK Contributors
# SPDX-License-Identifier: MIT
description: Set underglow to specified color
compatible: "zmk,behavior-underglow-color"
include: one_param.yaml

View File

@ -0,0 +1,13 @@
# Copyright (c) 2024, The ZMK Contributors
# SPDX-License-Identifier: MIT
description: Set underglow for HID indicators
compatible: "zmk,behavior-underglow-indicators"
include: two_param.yaml
properties:
indicator:
type: int
default: 0

View File

@ -0,0 +1,24 @@
description: |
Allows defining a rgbmap composed of multiple layers
compatible: "zmk,underglow-layer"
properties:
pixel-lookup:
type: array
required: true
child-binding:
description: "A layer to be used in a rgbmap"
properties:
bindings:
type: phandle-array
required: true
layer-id:
type: int
required: true
fade-delay:
type: int
required: false
default: -1

View File

@ -0,0 +1,9 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define NUM_LOCK 0
#define CAPS_LOCK 1
#define SCROLL_LOCK 2

View File

@ -0,0 +1,18 @@
/*
* Copyright (c) 2021 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define GREEN 0x00ff00
#define RED 0xff0000
#define BLUE 0x0000ff
#define TEAL 0x008080
#define ORANGE 0xffa500
#define YELLOW 0xffff00
#define GOLD 0xffd700
#define PURPLE 0x800080
#define PINK 0xffc0cb
#define WHITE 0xffffff
#define ___ 0x000000
#define BLACK 0x000000

View File

@ -0,0 +1,16 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <zephyr/kernel.h>
#include <zmk/event_manager.h>
struct zmk_split_peripheral_layer_changed {
uint32_t layers;
};
ZMK_EVENT_DECLARE(zmk_split_peripheral_layer_changed);

View File

@ -0,0 +1,16 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <zmk/event_manager.h>
struct zmk_underglow_color_changed {
uint32_t layers;
bool wakeup;
};
ZMK_EVENT_DECLARE(zmk_underglow_color_changed);

View File

@ -16,6 +16,8 @@ int zmk_rgb_underglow_toggle(void);
int zmk_rgb_underglow_get_state(bool *state);
int zmk_rgb_underglow_on(void);
int zmk_rgb_underglow_off(void);
int zmk_rgb_underglow_transient_on(void);
int zmk_rgb_underglow_transient_off(void);
int zmk_rgb_underglow_cycle_effect(int direction);
int zmk_rgb_underglow_calc_effect(int direction);
int zmk_rgb_underglow_select_effect(int effect);

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <zmk/keymap.h>
#define ZMK_RGB_CHILD_LEN_PLUS_ONE(node) 1 +
#define ZMK_RGBMAP_LAYERS_LEN \
(DT_FOREACH_CHILD(DT_INST(0, zmk_underglow_layer), ZMK_RGB_CHILD_LEN_PLUS_ONE) 0)
#define ZMK_RGBMAP_EXTRACT_BINDING(idx, drv_inst) \
{ \
.behavior_dev = DEVICE_DT_NAME(DT_PHANDLE_BY_IDX(drv_inst, bindings, idx)), \
.param1 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(drv_inst, bindings, idx, param1), (0), \
(DT_PHA_BY_IDX(drv_inst, bindings, idx, param1))), \
.param2 = COND_CODE_0(DT_PHA_HAS_CELL_AT_IDX(drv_inst, bindings, idx, param2), (0), \
(DT_PHA_BY_IDX(drv_inst, bindings, idx, param2))), \
}
const int rgb_pixel_lookup(int idx);
const int zmk_rgbmap_id(uint8_t layer);
const int zmk_rgbmap_fade_delay(uint8_t layer);
const struct zmk_behavior_binding *rgb_underglow_get_bindings(uint8_t layer);
uint8_t rgb_underglow_top_layer_with_state(uint32_t state_to_test);
uint8_t rgb_underglow_top_layer(void);

View File

@ -20,3 +20,4 @@
#define ZMK_SPLIT_BT_UPDATE_HID_INDICATORS_UUID ZMK_BT_SPLIT_UUID(0x00000004)
#define ZMK_SPLIT_BT_SELECT_PHYS_LAYOUT_UUID ZMK_BT_SPLIT_UUID(0x00000005)
#define ZMK_SPLIT_BT_INPUT_EVENT_UUID ZMK_BT_SPLIT_UUID(0x00000006)
#define ZMK_SPLIT_BT_UPDATE_LAYERS_UUID ZMK_BT_SPLIT_UUID(0x00000007)

View File

@ -46,3 +46,5 @@ int zmk_split_central_update_hid_indicator(zmk_hid_indicators_t indicators);
int zmk_split_central_get_peripheral_battery_level(uint8_t source, uint8_t *level);
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
int zmk_split_central_update_layers(uint32_t layers);

View File

@ -0,0 +1,5 @@
#pragma once
void set_peripheral_layers_state(uint32_t new_layers);
bool peripheral_layer_active(uint8_t layer);
uint8_t peripheral_highest_layer_active(void);

View File

@ -66,6 +66,7 @@ enum zmk_split_transport_central_command_type {
ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_INVOKE_BEHAVIOR,
ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_PHYSICAL_LAYOUT,
ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_HID_INDICATORS,
ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_RGB_LAYERS,
} __packed;
struct zmk_split_transport_central_command {
@ -87,5 +88,9 @@ struct zmk_split_transport_central_command {
struct {
zmk_hid_indicators_t indicators;
} set_hid_indicators;
struct {
uint32_t layers;
} set_rgb_layers;
} data;
} __packed;

View File

@ -16,6 +16,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/event_manager.h>
#include <zmk/events/activity_state_changed.h>
#include <zmk/events/position_state_changed.h>
#include <zmk/events/split_peripheral_layer_changed.h>
#include <zmk/events/sensor_event.h>
#include <zmk/pm.h>
@ -109,6 +110,9 @@ static int activity_init(void) {
ZMK_LISTENER(activity, activity_event_listener);
ZMK_SUBSCRIPTION(activity, zmk_position_state_changed);
ZMK_SUBSCRIPTION(activity, zmk_sensor_event);
#if IS_ENABLED(CONFIG_ZMK_SPLIT)
ZMK_SUBSCRIPTION(activity, zmk_split_peripheral_layer_changed);
#endif
#if IS_ENABLED(CONFIG_ZMK_POINTING)

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_behavior_underglow_battery
// Dependencies
#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
#include <zmk/battery.h>
#include <zmk/event_manager.h>
#include <zmk/events/battery_state_changed.h>
#include <zmk/events/underglow_color_changed.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
struct underglow_battery_data {
uint32_t layers;
};
struct underglow_battery_config {
int threshold;
};
static struct underglow_battery_data underglow_battery_data = {.layers = 0};
static int underglow_battery_init(const struct device *dev) { return 0; };
static int underglow_battery_process(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
const struct device *dev = zmk_behavior_get_binding(binding->behavior_dev);
const struct underglow_battery_config *config = dev->config;
struct underglow_battery_data *data = dev->data;
data->layers |= BIT(event.layer);
int bat = zmk_battery_state_of_charge();
if (bat >= config->threshold)
return binding->param2;
else
return binding->param1;
}
static const struct behavior_driver_api underglow_battery_driver_api = {
.binding_pressed = underglow_battery_process,
.locality = BEHAVIOR_LOCALITY_GLOBAL,
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
.get_parameter_metadata = zmk_behavior_get_empty_param_metadata,
#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
};
static int underglow_battery_listener(const zmk_event_t *eh);
ZMK_LISTENER(behavior_underglow_battery, underglow_battery_listener);
ZMK_SUBSCRIPTION(behavior_underglow_battery, zmk_battery_state_changed);
static int underglow_battery_listener(const zmk_event_t *eh) {
raise_zmk_underglow_color_changed((struct zmk_underglow_color_changed){
.layers = underglow_battery_data.layers, .wakeup = false});
return ZMK_EV_EVENT_BUBBLE;
}
#define KP_INST(n) \
static struct underglow_battery_config underglow_battery_config_##n = { \
.threshold = DT_INST_PROP(n, threshold)}; \
BEHAVIOR_DT_INST_DEFINE(n, underglow_battery_init, NULL, &underglow_battery_data, \
&underglow_battery_config_##n, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &underglow_battery_driver_api);
DT_INST_FOREACH_STATUS_OKAY(KP_INST)
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_behavior_underglow_color
// Dependencies
#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
// Initialization Function
static int underglow_color_init(const struct device *dev) { return 0; };
static int underglow_color_process(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
return binding->param1;
}
// API Structure
static const struct behavior_driver_api underglow_color_driver_api = {
.binding_pressed = underglow_color_process,
.locality = BEHAVIOR_LOCALITY_GLOBAL,
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
.get_parameter_metadata = zmk_behavior_get_empty_param_metadata,
#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
};
BEHAVIOR_DT_INST_DEFINE(0, underglow_color_init, NULL, NULL, NULL, POST_KERNEL,
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &underglow_color_driver_api);
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */

View File

@ -0,0 +1,83 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_behavior_underglow_indicators
// Dependencies
#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/logging/log.h>
#include <zmk/hid_indicators.h>
#include <zmk/event_manager.h>
#include <zmk/events/hid_indicators_changed.h>
#include <zmk/events/underglow_color_changed.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
struct underglow_indicators_data {
zmk_hid_indicators_t indicators;
uint32_t layers;
};
struct underglow_indicators_config {
int indicator;
};
static int underglow_indicators_init(const struct device *dev) { return 0; };
static int underglow_indicators_process(struct zmk_behavior_binding *binding,
struct zmk_behavior_binding_event event) {
const struct device *dev = zmk_behavior_get_binding(binding->behavior_dev);
if (dev == NULL) {
return binding->param1;
}
struct underglow_indicators_data *data = dev->data;
const struct underglow_indicators_config *config = dev->config;
data->layers |= BIT(event.layer);
if (data->indicators & BIT(config->indicator))
return binding->param2;
else
return binding->param1;
}
static const struct behavior_driver_api underglow_indicators_driver_api = {
.binding_pressed = underglow_indicators_process,
.locality = BEHAVIOR_LOCALITY_GLOBAL,
#if IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
.get_parameter_metadata = zmk_behavior_get_empty_param_metadata,
#endif // IS_ENABLED(CONFIG_ZMK_BEHAVIOR_METADATA)
};
static int underglow_indicators_listener(const zmk_event_t *eh);
ZMK_LISTENER(behavior_underglow_indicators, underglow_indicators_listener);
ZMK_SUBSCRIPTION(behavior_underglow_indicators, zmk_hid_indicators_changed);
static struct underglow_indicators_data underglow_indicators_data = {.indicators = 0, .layers = 0};
static int underglow_indicators_listener(const zmk_event_t *eh) {
const struct zmk_hid_indicators_changed *ev = as_zmk_hid_indicators_changed(eh);
underglow_indicators_data.indicators = ev->indicators;
raise_zmk_underglow_color_changed((struct zmk_underglow_color_changed){
.layers = underglow_indicators_data.layers, .wakeup = true});
return ZMK_EV_EVENT_BUBBLE;
}
#define KP_INST(n) \
static struct underglow_indicators_config underglow_indicators_config_##n = { \
.indicator = DT_INST_PROP(n, indicator)}; \
BEHAVIOR_DT_INST_DEFINE(n, underglow_indicators_init, NULL, &underglow_indicators_data, \
&underglow_indicators_config_##n, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
&underglow_indicators_driver_api);
DT_INST_FOREACH_STATUS_OKAY(KP_INST)
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */

View File

@ -0,0 +1,10 @@
/*
* Copyright (c) 2022 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/kernel.h>
#include <zmk/events/split_peripheral_layer_changed.h>
ZMK_EVENT_IMPL(zmk_split_peripheral_layer_changed);

View File

@ -0,0 +1,10 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/kernel.h>
#include <zmk/events/underglow_color_changed.h>
ZMK_EVENT_IMPL(zmk_underglow_color_changed);

View File

@ -17,10 +17,14 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/matrix.h>
#include <zmk/sensors.h>
#include <zmk/virtual_key_position.h>
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE)
#include <zmk/split/central.h>
#endif
#include <zmk/event_manager.h>
#include <zmk/events/position_state_changed.h>
#include <zmk/events/layer_state_changed.h>
#include <zmk/events/split_peripheral_layer_changed.h>
#include <zmk/events/sensor_event.h>
static zmk_keymap_layers_state_t _zmk_keymap_layer_locks = 0;
@ -161,6 +165,11 @@ static inline int set_layer_state(zmk_keymap_layer_id_t layer_id, bool state, bo
if (ret < 0) {
LOG_WRN("Failed to raise layer state changed (%d)", ret);
}
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE)
zmk_split_central_update_layers(_zmk_keymap_layer_state);
raise_zmk_split_peripheral_layer_changed(
(struct zmk_split_peripheral_layer_changed){.layers = _zmk_keymap_layer_state});
#endif
}
return ret;

View File

@ -16,15 +16,27 @@
#include <zephyr/drivers/led_strip.h>
#include <drivers/ext_power.h>
#include <drivers/behavior.h>
#include <zmk/rgb_underglow.h>
#include <zmk/rgb_underglow_layer.h>
#include <zmk/activity.h>
#include <zmk/behavior.h>
#include <zmk/matrix.h>
#include <zmk/hid_indicators.h>
#include <zmk/usb.h>
#include <zmk/event_manager.h>
#include <zmk/events/activity_state_changed.h>
#include <zmk/events/usb_conn_state_changed.h>
#include <zmk/events/underglow_color_changed.h>
#include <zmk/workqueue.h>
#include <zmk/events/split_peripheral_layer_changed.h>
#if !IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
#include <zmk/split/peripheral_layers.h>
#endif
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
@ -37,6 +49,11 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#define STRIP_CHOSEN DT_CHOSEN(zmk_underglow)
#define STRIP_NUM_PIXELS DT_PROP(STRIP_CHOSEN, chain_length)
#if DT_HAS_COMPAT_STATUS_OKAY(zmk_underglow_layer) && IS_ENABLED(CONFIG_EXPERIMENTAL_RGB_LAYER)
#define UNDERGLOW_LAYER_ENABLED 1
static void zmk_rgb_underglow_set_layer(uint8_t layer, bool wakeup);
#endif
#define HUE_MAX 360
#define SAT_MAX 100
#define BRT_MAX 100
@ -49,6 +66,9 @@ enum rgb_underglow_effect {
UNDERGLOW_EFFECT_BREATHE,
UNDERGLOW_EFFECT_SPECTRUM,
UNDERGLOW_EFFECT_SWIRL,
#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED)
UNDERGLOW_EFFECT_LAYER_INDICATORS,
#endif
UNDERGLOW_EFFECT_NUMBER // Used to track number of underglow effects
};
@ -58,6 +78,7 @@ struct rgb_underglow_state {
uint8_t current_effect;
uint16_t animation_step;
bool on;
bool layer_enabled;
};
static const struct device *led_strip;
@ -175,6 +196,25 @@ static void zmk_rgb_underglow_effect_swirl(void) {
state.animation_step = state.animation_step % HUE_MAX;
}
#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED)
static void zmk_rgb_underglow_effect_layer(void) {
bool active = false;
for (int i = 0; i < STRIP_NUM_PIXELS; i++) {
pixels[i].r -= state.animation_speed < pixels[i].r ? state.animation_speed : pixels[i].r;
pixels[i].g -= state.animation_speed < pixels[i].g ? state.animation_speed : pixels[i].g;
pixels[i].b -= state.animation_speed < pixels[i].b ? state.animation_speed : pixels[i].b;
if (pixels[i].r || pixels[i].g || pixels[i].b) {
active = true;
}
}
state.animation_step += state.animation_speed;
if (state.animation_step > 255 || !active) {
zmk_rgb_underglow_transient_off();
}
}
#endif // IS_ENABLED(UNDERGLOW_LAYER_ENABLED)
static void zmk_rgb_underglow_tick(struct k_work *work) {
switch (state.current_effect) {
case UNDERGLOW_EFFECT_SOLID:
@ -189,6 +229,11 @@ static void zmk_rgb_underglow_tick(struct k_work *work) {
case UNDERGLOW_EFFECT_SWIRL:
zmk_rgb_underglow_effect_swirl();
break;
#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED)
case UNDERGLOW_EFFECT_LAYER_INDICATORS:
zmk_rgb_underglow_effect_layer();
break;
#endif
}
int err = led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS);
@ -224,7 +269,11 @@ 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 IS_ENABLED(UNDERGLOW_LAYER_ENABLED)
if (state.layer_enabled) {
zmk_rgb_underglow_set_layer(rgb_underglow_top_layer(), true);
}
#endif
return 0;
}
@ -276,7 +325,11 @@ static int zmk_rgb_underglow_init(void) {
if (state.on) {
k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50));
}
#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED)
if (state.layer_enabled) {
zmk_rgb_underglow_set_layer(rgb_underglow_top_layer(), true);
}
#endif
return 0;
}
@ -293,11 +346,21 @@ 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;
}
int zmk_rgb_underglow_on(void) {
zmk_rgb_underglow_transient_on();
#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED)
if (state.current_effect == UNDERGLOW_EFFECT_LAYER_INDICATORS) {
state.layer_enabled = true;
}
#endif
return zmk_rgb_underglow_save_state();
}
int zmk_rgb_underglow_transient_on(void) {
if (!led_strip)
return -ENODEV;
@ -314,7 +377,7 @@ int zmk_rgb_underglow_on(void) {
state.animation_step = 0;
k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50));
return zmk_rgb_underglow_save_state();
return 0;
}
static void zmk_rgb_underglow_off_handler(struct k_work *work) {
@ -328,6 +391,12 @@ static void zmk_rgb_underglow_off_handler(struct k_work *work) {
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();
}
int zmk_rgb_underglow_transient_off(void) {
if (!led_strip)
return -ENODEV;
@ -345,7 +414,7 @@ int zmk_rgb_underglow_off(void) {
k_timer_stop(&underglow_tick);
state.on = false;
return zmk_rgb_underglow_save_state();
return 0;
}
int zmk_rgb_underglow_calc_effect(int direction) {
@ -362,7 +431,9 @@ int zmk_rgb_underglow_select_effect(int effect) {
state.current_effect = effect;
state.animation_step = 0;
#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED)
state.layer_enabled = (effect == UNDERGLOW_EFFECT_LAYER_INDICATORS);
#endif
return zmk_rgb_underglow_save_state();
}
@ -374,6 +445,85 @@ int zmk_rgb_underglow_toggle(void) {
return state.on ? zmk_rgb_underglow_off() : zmk_rgb_underglow_on();
}
#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED)
static struct led_rgb hex_to_rgb(uint8_t r, uint8_t g, uint8_t b) {
struct zmk_led_hsb hsb = state.color;
return (struct led_rgb){
r : (hsb.b * (r)) / 0xff,
g : (hsb.b * (g)) / 0xff,
b : (hsb.b * (b)) / 0xff
};
}
static int zmk_rgb_underglow_apply_rgbmap(const struct zmk_behavior_binding *bindings,
size_t rgbmap_len, uint8_t layer) {
int rc = 0;
for (int i = 0; i < STRIP_NUM_PIXELS; i++) {
uint8_t midx = rgb_pixel_lookup(i);
if (midx >= ZMK_KEYMAP_LEN) {
LOG_DBG("out of range");
} else {
const struct device *dev = zmk_behavior_get_binding(bindings[midx].behavior_dev);
if (dev == NULL) {
continue;
}
const struct behavior_driver_api *api = (const struct behavior_driver_api *)dev->api;
if (api->binding_pressed == NULL) {
continue;
}
struct zmk_behavior_binding_event event = {
.position = midx, .layer = layer, .timestamp = k_uptime_get()};
int color = api->binding_pressed((struct zmk_behavior_binding *)&bindings[midx], event);
if (color > 0) {
pixels[i] =
hex_to_rgb((color & 0xFF0000) >> 16, (color & 0xFF00) >> 8, color & 0xFF);
rc = 1;
} else {
pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0};
}
}
}
return rc;
}
static void zmk_rgb_underglow_set_layer(uint8_t layer, bool wakeup) {
LOG_DBG("state.layer: %d state.on: %d", state.layer_enabled, state.on);
if (!state.layer_enabled)
return;
const struct zmk_behavior_binding *rgbmap = rgb_underglow_get_bindings(layer);
if (rgbmap != NULL && zmk_rgb_underglow_apply_rgbmap(rgbmap, ZMK_KEYMAP_LEN, layer)) {
if (!state.on) {
if (!wakeup) {
LOG_DBG("rgb off and no wakeup, abort refresh");
return;
}
zmk_rgb_underglow_transient_on();
}
k_timer_stop(&underglow_tick);
state.animation_step = 0;
int fade_delay = zmk_rgbmap_fade_delay(layer);
if (fade_delay >= 0) {
k_timer_start(&underglow_tick, K_SECONDS(fade_delay), K_MSEC(50));
}
LOG_DBG("write pixels");
int err = led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS);
if (err < 0) {
LOG_ERR("Failed to update the RGB strip (%d)", err);
}
} else {
if (state.on)
zmk_rgb_underglow_transient_off();
}
}
#endif /* IS_ENABLED(UNDERGLOW_LAYER_ENABLED) */
int zmk_rgb_underglow_set_hsb(struct zmk_led_hsb color) {
if (color.h > HUE_MAX || color.s > SAT_MAX || color.b > BRT_MAX) {
return -ENOTSUP;
@ -461,7 +611,7 @@ int zmk_rgb_underglow_change_spd(int direction) {
}
#if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE) || \
IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB)
IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) || IS_ENABLED(UNDERGLOW_LAYER_ENABLED)
struct rgb_underglow_sleep_state {
bool is_awake;
bool rgb_state_before_sleeping;
@ -480,14 +630,20 @@ static int rgb_underglow_auto_state(bool target_wake_state) {
sleep_state.is_awake = target_wake_state;
if (sleep_state.is_awake) {
#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED)
if (state.layer_enabled) {
zmk_rgb_underglow_set_layer(rgb_underglow_top_layer(), true);
return 0;
}
#endif
if (sleep_state.rgb_state_before_sleeping) {
return zmk_rgb_underglow_on();
return zmk_rgb_underglow_transient_on();
} else {
return zmk_rgb_underglow_off();
return zmk_rgb_underglow_transient_off();
}
} else {
sleep_state.rgb_state_before_sleeping = state.on;
return zmk_rgb_underglow_off();
return zmk_rgb_underglow_transient_off();
}
}
@ -499,6 +655,30 @@ static int rgb_underglow_event_listener(const zmk_event_t *eh) {
}
#endif
#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED)
if (as_zmk_split_peripheral_layer_changed(eh)) {
const struct zmk_split_peripheral_layer_changed *ev =
as_zmk_split_peripheral_layer_changed(eh);
LOG_DBG("zmk_split_peripheral_layer_changed: %08x", ev->layers);
#if !IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
set_peripheral_layers_state(ev->layers);
#endif
uint8_t layer = rgb_underglow_top_layer();
LOG_DBG("top layer: %d", layer);
zmk_rgb_underglow_set_layer(layer, true);
return 0;
}
if (as_zmk_underglow_color_changed(eh)) {
const struct zmk_underglow_color_changed *ev = as_zmk_underglow_color_changed(eh);
uint8_t layer = rgb_underglow_top_layer();
LOG_DBG("refresh layers %d, current: %d, wakeup: %d", ev->layers, layer, ev->wakeup);
if ((ev->layers & (BIT(layer))) == BIT(layer)) {
zmk_rgb_underglow_set_layer(rgb_underglow_top_layer(), ev->wakeup);
}
return 0;
}
#endif /* UNDERGLOW_LAYER_ENABLED */
#if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB)
if (as_zmk_usb_conn_state_changed(eh)) {
return rgb_underglow_auto_state(zmk_usb_is_powered());
@ -510,7 +690,8 @@ static int rgb_underglow_event_listener(const zmk_event_t *eh) {
ZMK_LISTENER(rgb_underglow, rgb_underglow_event_listener);
#endif // IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE) ||
// IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB)
// IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) ||
// IS_ENABLED(UNDERGLOW_LAYER_ENABLED)
#if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_IDLE)
ZMK_SUBSCRIPTION(rgb_underglow, zmk_activity_state_changed);
@ -520,4 +701,9 @@ ZMK_SUBSCRIPTION(rgb_underglow, zmk_activity_state_changed);
ZMK_SUBSCRIPTION(rgb_underglow, zmk_usb_conn_state_changed);
#endif
#if IS_ENABLED(UNDERGLOW_LAYER_ENABLED)
ZMK_SUBSCRIPTION(rgb_underglow, zmk_split_peripheral_layer_changed);
ZMK_SUBSCRIPTION(rgb_underglow, zmk_underglow_color_changed);
#endif
SYS_INIT(zmk_rgb_underglow_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);

View File

@ -0,0 +1,84 @@
/*
* Copyright (c) 2020 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/device.h>
#include <drivers/behavior.h>
#include <zephyr/sys/util.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/matrix.h>
#include <zmk/keymap.h>
#include <zmk/rgb_underglow_layer.h>
#if !IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
#include <zmk/split/peripheral_layers.h>
#endif
#define DT_DRV_COMPAT zmk_underglow_layer
#if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT)
#define UNDERGLOW_LAYER_ENABLED
#define LAYER_ID(node) DT_PROP(node, layer_id)
#define FADE_DELAY(node) DT_PROP(node, fade_delay)
#define TRANSFORMED_RGB_LAYER(node) \
{COND_CODE_1(DT_NODE_HAS_PROP(node, bindings), \
(LISTIFY(DT_PROP_LEN(node, bindings), ZMK_RGBMAP_EXTRACT_BINDING, (, ), node)), \
())}
#define RGBMAP_VAR(_name, _opts) \
static _opts struct zmk_behavior_binding _name[ZMK_RGBMAP_LAYERS_LEN][ZMK_KEYMAP_LEN] = { \
DT_INST_FOREACH_CHILD_STATUS_OKAY_SEP(0, TRANSFORMED_RGB_LAYER, (, ))};
RGBMAP_VAR(zmk_rgbmap, COND_CODE_1(IS_ENABLED(CONFIG_ZMK_KEYMAP_SETTINGS_STORAGE), (), (const)))
const int pixel_lookup_table[] = DT_INST_PROP(0, pixel_lookup);
static int zmk_rgbmap_ids[ZMK_RGBMAP_LAYERS_LEN] = {DT_INST_FOREACH_CHILD_SEP(0, LAYER_ID, (, ))};
static int zmk_rgbmap_fds[ZMK_RGBMAP_LAYERS_LEN] = {DT_INST_FOREACH_CHILD_SEP(0, FADE_DELAY, (, ))};
const int rgb_pixel_lookup(int idx) { return pixel_lookup_table[idx]; };
const int zmk_rgbmap_id(uint8_t layer) {
for (uint8_t i = 0; i < ZMK_RGBMAP_LAYERS_LEN; i++) {
if (zmk_rgbmap_ids[i] == layer) {
return i;
}
}
return -1;
}
const int zmk_rgbmap_fade_delay(uint8_t layer) { return zmk_rgbmap_fds[zmk_rgbmap_id(layer)]; }
const struct zmk_behavior_binding *rgb_underglow_get_bindings(uint8_t layer) {
int rgblayer = zmk_rgbmap_id(layer);
if (rgblayer == -1) {
return NULL;
} else {
return zmk_rgbmap[rgblayer];
}
}
uint8_t rgb_underglow_top_layer_with_state(uint32_t state_to_test) {
for (uint8_t layer = ZMK_KEYMAP_LAYERS_LEN - 1; layer > 0; layer--) {
if ((state_to_test & (BIT(layer))) == (BIT(layer)) || layer == 0) {
return layer;
}
}
// return default layer (0)
return 0;
}
uint8_t rgb_underglow_top_layer(void) {
#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
return zmk_keymap_highest_layer_active();
#else
return peripheral_highest_layer_active();
#endif
}
#endif /* DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) */

View File

@ -14,5 +14,6 @@ if (CONFIG_ZMK_SPLIT_ROLE_CENTRAL)
zephyr_linker_sources(SECTIONS ../../include/linker/zmk-split-transport-central.ld)
else()
target_sources(app PRIVATE peripheral.c)
target_sources(app PRIVATE peripheral_layers.c)
zephyr_linker_sources(SECTIONS ../../include/linker/zmk-split-transport-peripheral.ld)
endif()

View File

@ -33,6 +33,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/pointing/input_split.h>
#include <zmk/hid_indicators_types.h>
#include <zmk/physical_layouts.h>
#include <zmk/events/split_peripheral_layer_changed.h>
static int start_scanning(void);
@ -60,6 +61,8 @@ struct peripheral_slot {
uint16_t update_hid_indicators;
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
uint16_t selected_physical_layout_handle;
uint16_t update_layers_handle;
uint8_t position_state[POSITION_STATE_DATA_LEN];
uint8_t changed_positions[POSITION_STATE_DATA_LEN];
};
@ -219,6 +222,7 @@ int release_peripheral_slot(int index) {
#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
slot->update_hid_indicators = 0;
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
slot->update_layers_handle = 0;
return 0;
}
@ -620,6 +624,10 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn,
LOG_DBG("Found update HID indicators handle");
slot->update_hid_indicators = bt_gatt_attr_value_handle(attr);
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
} else if (!bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid,
BT_UUID_DECLARE_128(ZMK_SPLIT_BT_UPDATE_LAYERS_UUID))) {
LOG_DBG("Found update Layers handle");
slot->update_layers_handle = bt_gatt_attr_value_handle(attr);
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
} else if (!bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid,
BT_UUID_BAS_BATTERY_LEVEL)) {
@ -707,6 +715,8 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn,
}
#endif // IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT)
subscribed = subscribed && slot->update_layers_handle;
return subscribed ? BT_GATT_ITER_STOP : BT_GATT_ITER_CONTINUE;
}
@ -1024,6 +1034,7 @@ K_MSGQ_DEFINE(zmk_split_central_split_run_msgq, sizeof(struct central_cmd_wrappe
void split_central_split_run_callback(struct k_work *work) {
struct central_cmd_wrapper payload_wrapper;
int err;
LOG_DBG("");
@ -1056,7 +1067,7 @@ void split_central_split_run_callback(struct k_work *work) {
payload.behavior_dev);
}
int err = bt_gatt_write_without_response(
err = bt_gatt_write_without_response(
peripherals[payload_wrapper.source].conn,
peripherals[payload_wrapper.source].run_behavior_handle, &payload,
sizeof(struct zmk_split_run_behavior_payload), true);
@ -1082,7 +1093,7 @@ void split_central_split_run_callback(struct k_work *work) {
break;
}
int err = bt_gatt_write_without_response(
err = bt_gatt_write_without_response(
peripherals[payload_wrapper.source].conn,
peripherals[payload_wrapper.source].update_hid_indicators,
&payload_wrapper.cmd.data.set_hid_indicators.indicators,
@ -1093,6 +1104,18 @@ void split_central_split_run_callback(struct k_work *work) {
}
break;
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_RGB_LAYERS:
err = bt_gatt_write_without_response(
peripherals[payload_wrapper.source].conn,
peripherals[payload_wrapper.source].update_layers_handle,
&payload_wrapper.cmd.data.set_rgb_layers.layers,
sizeof(payload_wrapper.cmd.data.set_rgb_layers.layers), true);
if (err) {
LOG_ERR("Failed to send layers to peripheral (err %d)", err);
}
break;
default:
LOG_WRN("Unsupported wrapped central command type %d", payload_wrapper.cmd.type);
return;
@ -1174,6 +1197,7 @@ static int split_central_bt_send_command(uint8_t source,
}
switch (cmd.type) {
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_RGB_LAYERS:
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_HID_INDICATORS:
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_PHYSICAL_LAYOUT:
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_INVOKE_BEHAVIOR: {

View File

@ -31,9 +31,11 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
#include <zmk/events/hid_indicators_changed.h>
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
#include <zmk/split/peripheral_layers.h>
#include <zmk/events/sensor_event.h>
#include <zmk/sensors.h>
#include <zmk/events/split_peripheral_layer_changed.h>
#if ZMK_KEYMAP_HAS_SENSORS
static struct sensor_event last_sensor_event;
@ -139,6 +141,31 @@ static ssize_t split_svc_get_selected_phys_layout(struct bt_conn *conn,
return bt_gatt_attr_read(conn, attrs, buf, len, offset, &selected, sizeof(selected));
}
static uint32_t layers = 0;
static void split_svc_update_layers_callback(struct k_work *work) {
LOG_DBG("Setting peripheral layers: %x", layers);
// set_peripheral_layers_state(layers);
raise_zmk_split_peripheral_layer_changed(
(struct zmk_split_peripheral_layer_changed){.layers = layers});
}
static K_WORK_DEFINE(split_svc_update_layers_work, split_svc_update_layers_callback);
static ssize_t split_svc_update_layers(struct bt_conn *conn, const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset,
uint8_t flags) {
if (offset + len > sizeof(uint32_t)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}
memcpy((uint8_t *)&layers + offset, buf, len);
k_work_submit(&split_svc_update_layers_work);
return len;
}
#if IS_ENABLED(CONFIG_ZMK_INPUT_SPLIT)
static void split_input_events_ccc(const struct bt_gatt_attr *attr, uint16_t value) {
@ -204,8 +231,11 @@ BT_GATT_SERVICE_DEFINE(
BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_SELECT_PHYS_LAYOUT_UUID),
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_READ,
BT_GATT_PERM_WRITE_ENCRYPT | BT_GATT_PERM_READ_ENCRYPT,
split_svc_get_selected_phys_layout, split_svc_select_phys_layout,
NULL), );
split_svc_get_selected_phys_layout, split_svc_select_phys_layout, NULL),
BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_UPDATE_LAYERS_UUID),
BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE_ENCRYPT, NULL,
split_svc_update_layers, NULL), );
K_THREAD_STACK_DEFINE(service_q_stack, CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_STACK_SIZE);

View File

@ -150,6 +150,42 @@ int zmk_split_central_update_hid_indicator(zmk_hid_indicators_t indicators) {
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
int zmk_split_central_update_layers(uint32_t new_layers) {
if (!active_transport || !active_transport->api ||
!active_transport->api->get_available_source_ids || !active_transport->api->send_command) {
return -ENODEV;
}
uint8_t source_ids[ZMK_SPLIT_CENTRAL_PERIPHERAL_COUNT];
int ret = active_transport->api->get_available_source_ids(source_ids);
if (ret < 0) {
return ret;
}
struct zmk_split_transport_central_command command =
(struct zmk_split_transport_central_command){
.type = ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_RGB_LAYERS,
.data =
{
.set_rgb_layers =
{
.layers = new_layers,
},
},
};
for (size_t i = 0; i < ret; i++) {
ret = active_transport->api->send_command(source_ids[i], command);
if (ret < 0) {
return ret;
}
}
return 0;
}
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
int zmk_split_central_get_peripheral_battery_level(uint8_t source, uint8_t *level) {

View File

@ -21,6 +21,7 @@
#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
#include <zmk/events/hid_indicators_changed.h>
#endif
#include <zmk/events/split_peripheral_layer_changed.h>
#include <zephyr/init.h>
#include <zephyr/logging/log.h>
@ -66,6 +67,10 @@ int zmk_split_transport_peripheral_command_handler(
.indicators = cmd.data.set_hid_indicators.indicators});
}
#endif
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_RGB_LAYERS: {
return raise_zmk_split_peripheral_layer_changed(
(struct zmk_split_peripheral_layer_changed){.layers = cmd.data.set_rgb_layers.layers});
}
default:
LOG_WRN("Unhandled command type %d", cmd.type);
return -ENOTSUP;

View File

@ -0,0 +1,25 @@
#include <zephyr/types.h>
#include <zephyr/sys/util.h>
#include <zmk/split/peripheral_layers.h>
#include <zmk/keymap.h>
static uint32_t peripheral_layers = 0;
void set_peripheral_layers_state(uint32_t new_layers) { peripheral_layers = new_layers; }
bool peripheral_layer_active(uint8_t layer) {
return (peripheral_layers & (BIT(layer))) == (BIT(layer));
};
uint8_t peripheral_highest_layer_active(void) {
if (peripheral_layers > 0) {
for (uint8_t layer = ZMK_KEYMAP_LAYERS_LEN - 1; layer > 0; layer--) {
if ((peripheral_layers & (BIT(layer))) == (BIT(layer)) || layer == 0) {
return layer;
}
}
}
return 0;
}

View File

@ -185,6 +185,8 @@ static ssize_t get_payload_data_size(const struct zmk_split_transport_central_co
return sizeof(cmd->data.set_physical_layout);
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_HID_INDICATORS:
return sizeof(cmd->data.set_hid_indicators);
case ZMK_SPLIT_TRANSPORT_CENTRAL_CMD_TYPE_SET_RGB_LAYERS:
return sizeof(cmd->data.set_rgb_layers);
default:
return -ENOTSUP;
}