mirror of
https://github.com/qmk/qmk_firmware.git
synced 2026-09-26 03:49:20 +02:00
Initial PoC for Dynamic lighting support
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
// Copyright 2024 QMK
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include <string.h> // for memcpy
|
||||
#include "lamparray.h"
|
||||
#include "lamparray_surface.h"
|
||||
#include "keycodes.h"
|
||||
#include "keymap_introspection.h"
|
||||
#include "action_layer.h"
|
||||
|
||||
// Defaults are generated from info.json layout content
|
||||
#ifndef LAMPARRAY_WIDTH
|
||||
# define LAMPARRAY_WIDTH ESTIMATED_KEYBOARD_WIDTH
|
||||
#endif
|
||||
#ifndef LAMPARRAY_HEIGHT
|
||||
# define LAMPARRAY_HEIGHT ESTIMATED_KEYBOARD_HEIGHT
|
||||
#endif
|
||||
#ifndef LAMPARRAY_DEPTH
|
||||
# define LAMPARRAY_DEPTH 30000
|
||||
#endif
|
||||
#ifndef LAMPARRAY_KIND
|
||||
# define LAMPARRAY_KIND LAMPARRAY_KIND_KEYBOARD
|
||||
#endif
|
||||
|
||||
#ifdef RGB_MATRIX_ENABLE
|
||||
# include "rgb_matrix.h"
|
||||
# define LAMPARRAY_RED_LEVELS 255
|
||||
# define LAMPARRAY_GREEN_LEVELS 255
|
||||
# define LAMPARRAY_BLUE_LEVELS 255
|
||||
# define LAMPARRAY_INTENSITY_LEVELS 1
|
||||
# define LAMPARRAY_LAMP_COUNT RGB_MATRIX_LED_COUNT
|
||||
# define LAMPARRAY_UPDATE_INTERVAL (RGB_MATRIX_LED_FLUSH_LIMIT * 1000)
|
||||
#endif
|
||||
|
||||
//****************************************************************************
|
||||
// utils
|
||||
|
||||
/**
|
||||
* \brief Query a HID usage for a given location
|
||||
*
|
||||
* This can be requested while the user is changing layers. This is mitigated somewhat by assuming the default layer changes infrequently.
|
||||
* This is currently accepted as a limitation as there is no method to invalidate the hosts view of the data.
|
||||
*/
|
||||
uint8_t lamparray_binding_at_keymap_location(uint8_t row, uint8_t col) {
|
||||
uint16_t keycode = keycode_at_keymap_location(get_highest_layer(default_layer_state), row, col);
|
||||
(void)keycode;
|
||||
#if LAMPARRAY_KIND == LAMPARRAY_KIND_KEYBOARD
|
||||
// Basic QMK keycodes currently map directly to Keyboard UsagePage so safe to return without added indirection
|
||||
// Mousekeys are ignored due to values overlap Keyboard UsagePage
|
||||
if (IS_BASIC_KEYCODE(keycode) || IS_MODIFIER_KEYCODE(keycode)) {
|
||||
return keycode;
|
||||
}
|
||||
#elif LAMPARRAY_KIND == LAMPARRAY_KIND_MOUSE
|
||||
// Usages from the Button UsagePage (0x09) in the range of Button1 (0x01) to Button5 (0x05) inclusive
|
||||
if ((code) >= KC_MS_BTN1 && (code) <= KC_MS_BTN5) {
|
||||
return keycode - KC_MS_BTN1 + 1;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
//****************************************************************************
|
||||
// cache
|
||||
|
||||
static uint8_t input_binding_cache[LAMPARRAY_LAMP_COUNT];
|
||||
|
||||
void lamparray_update_cache(void) {
|
||||
for (uint8_t lamp_id = 0; lamp_id < LAMPARRAY_LAMP_COUNT; lamp_id++) {
|
||||
input_binding_cache[lamp_id] = lamparray_get_lamp_binding_impl(lamp_id);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t lamparray_get_lamp_binding(uint16_t lamp_id) {
|
||||
return input_binding_cache[lamp_id];
|
||||
}
|
||||
|
||||
//****************************************************************************
|
||||
// queue
|
||||
|
||||
#ifndef LAMPARRAY_REQUEST_QUEUE_SIZE
|
||||
# define LAMPARRAY_REQUEST_QUEUE_SIZE 5
|
||||
#endif
|
||||
|
||||
universal_lamparray_response_t request_queue[LAMPARRAY_REQUEST_QUEUE_SIZE] = {0};
|
||||
uint8_t queue_size = 0;
|
||||
|
||||
void lamparray_queue_request(universal_lamparray_response_t* report) {
|
||||
// get next slot
|
||||
universal_lamparray_response_t* target = &request_queue[queue_size++];
|
||||
|
||||
// copy data
|
||||
memcpy(target, report, sizeof(universal_lamparray_response_t));
|
||||
}
|
||||
|
||||
void lamparray_handle_queue(void) {
|
||||
for (uint8_t id = 0; id < queue_size; id++) {
|
||||
universal_lamparray_response_t* report = &request_queue[id];
|
||||
switch (report->report_id) {
|
||||
case LAMPARRAY_REPORT_ID_RANGE_UPDATE:
|
||||
lamparray_set_range(&report->range_update);
|
||||
break;
|
||||
case LAMPARRAY_REPORT_ID_MULTI_UPDATE:
|
||||
lamparray_set_items(&report->multi_update);
|
||||
break;
|
||||
case LAMPARRAY_REPORT_ID_CONTROL:
|
||||
lamparray_set_control_response(report->autonomous);
|
||||
break;
|
||||
}
|
||||
}
|
||||
queue_size = 0;
|
||||
}
|
||||
|
||||
//****************************************************************************
|
||||
// impl
|
||||
|
||||
static uint16_t cur_lamp_id = 0;
|
||||
static bool is_autonomous = true;
|
||||
|
||||
void lamparray_get_attributes(lamparray_attributes_t* data) {
|
||||
data->lamp_count = LAMPARRAY_LAMP_COUNT;
|
||||
data->update_interval = LAMPARRAY_UPDATE_INTERVAL;
|
||||
data->kind = LAMPARRAY_KIND;
|
||||
data->bounds.width = LAMPARRAY_WIDTH;
|
||||
data->bounds.height = LAMPARRAY_HEIGHT;
|
||||
data->bounds.depth = LAMPARRAY_DEPTH;
|
||||
}
|
||||
|
||||
void lamparray_get_attributes_response(lamparray_attributes_response_t* data) {
|
||||
data->lamp_id = cur_lamp_id;
|
||||
data->update_latency = 1000;
|
||||
data->is_programmable = 1;
|
||||
data->input_binding = lamparray_get_lamp_binding(cur_lamp_id);
|
||||
|
||||
data->levels.red = LAMPARRAY_RED_LEVELS;
|
||||
data->levels.green = LAMPARRAY_GREEN_LEVELS;
|
||||
data->levels.blue = LAMPARRAY_BLUE_LEVELS;
|
||||
data->levels.intensity = LAMPARRAY_INTENSITY_LEVELS;
|
||||
|
||||
lamparray_get_lamp_impl(cur_lamp_id, data);
|
||||
|
||||
// Automatic address pointer incrementing - 26.8.1 LampAttributesRequestReport
|
||||
cur_lamp_id++;
|
||||
if (cur_lamp_id >= LAMPARRAY_LAMP_COUNT) cur_lamp_id = 0;
|
||||
}
|
||||
|
||||
void lamparray_set_attributes_response(uint16_t lamp_id) {
|
||||
cur_lamp_id = lamp_id;
|
||||
}
|
||||
|
||||
void lamparray_set_control_response(uint8_t autonomous) {
|
||||
is_autonomous = !!autonomous;
|
||||
|
||||
lamparray_surface_enable(!autonomous);
|
||||
}
|
||||
|
||||
void lamparray_set_range(lamparray_range_update_t* data) {
|
||||
// Any Lamp*UpdateReports can be ignored - 26.10.1 AutonomousMode
|
||||
if (is_autonomous) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure IDs are within bounds
|
||||
if ((data->start >= LAMPARRAY_LAMP_COUNT) || (data->end >= LAMPARRAY_LAMP_COUNT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint16_t index = data->start; index <= data->end; index++) {
|
||||
lamparray_surface_set_item(index, data->color);
|
||||
}
|
||||
|
||||
// Batch update complete - 26.11 Updating Lamp State
|
||||
if (data->flags & LAMP_UPDATE_FLAG_COMPLETE) {
|
||||
lamparray_surface_update_finished();
|
||||
}
|
||||
}
|
||||
|
||||
void lamparray_set_items(lamparray_multi_update_t* data) {
|
||||
// Any Lamp*UpdateReports can be ignored - 26.10.1 AutonomousMode
|
||||
if (is_autonomous) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure data is within bounds
|
||||
if (data->count > LAMP_MULTI_UPDATE_LAMP_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < data->count; i++) {
|
||||
// Ensure IDs are within bounds
|
||||
if (data->ids[i] >= LAMPARRAY_LAMP_COUNT) {
|
||||
continue;
|
||||
}
|
||||
lamparray_surface_set_item(data->ids[i], data->colors[i]);
|
||||
}
|
||||
|
||||
// Batch update complete - 26.11 Updating Lamp State
|
||||
if (data->flags & LAMP_UPDATE_FLAG_COMPLETE) {
|
||||
lamparray_surface_update_finished();
|
||||
}
|
||||
}
|
||||
|
||||
//****************************************************************************
|
||||
// feature hooks
|
||||
|
||||
void lamparray_init(void) {
|
||||
lamparray_update_cache();
|
||||
}
|
||||
|
||||
void lamparray_task(void) {
|
||||
lamparray_handle_queue();
|
||||
|
||||
// TODO: regen cache if dynamic keymap updated?
|
||||
uint16_t temp = 0;
|
||||
if (!++temp) lamparray_update_cache();
|
||||
}
|
||||
|
||||
// TODO: SRC += ...
|
||||
#ifdef RGB_MATRIX_ENABLE
|
||||
# include "lamparray_rgb_matrix.c"
|
||||
#endif
|
||||
@@ -0,0 +1,159 @@
|
||||
// Copyright 2024 QMK
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "util.h" // PACKED
|
||||
|
||||
#define LAMPARRAY_REPORT_ID_ATTRIBUTES 0x01
|
||||
#define LAMPARRAY_REPORT_ID_ATTRIBUTES_REQUEST 0x02
|
||||
#define LAMPARRAY_REPORT_ID_ATTRIBUTES_RESPONSE 0x03
|
||||
#define LAMPARRAY_REPORT_ID_MULTI_UPDATE 0x04
|
||||
#define LAMPARRAY_REPORT_ID_RANGE_UPDATE 0x05
|
||||
#define LAMPARRAY_REPORT_ID_CONTROL 0x06
|
||||
|
||||
// 26.2.1 LampArrayKind Values
|
||||
#define LAMPARRAY_KIND_UNDEFINED 0x00
|
||||
#define LAMPARRAY_KIND_KEYBOARD 0x01
|
||||
#define LAMPARRAY_KIND_MOUSE 0x02
|
||||
#define LAMPARRAY_KIND_GAMECONTROLLER 0x03
|
||||
#define LAMPARRAY_KIND_PERIPHERAL 0x04
|
||||
#define LAMPARRAY_KIND_SCENE 0x05
|
||||
#define LAMPARRAY_KIND_NOTIFICATION 0x06
|
||||
#define LAMPARRAY_KIND_CHASSIS 0x07
|
||||
#define LAMPARRAY_KIND_WEARABLE 0x08
|
||||
#define LAMPARRAY_KIND_FURNITURE 0x09
|
||||
#define LAMPARRAY_KIND_ART 0x0A
|
||||
|
||||
// 26.3.1 LampPurposes Flags
|
||||
#define LAMP_PURPOSE_CONTROL 0x01
|
||||
#define LAMP_PURPOSE_ACCENT 0x02
|
||||
#define LAMP_PURPOSE_BRANDING 0x04
|
||||
#define LAMP_PURPOSE_STATUS 0x08
|
||||
#define LAMP_PURPOSE_ILLUMINATION 0x10
|
||||
#define LAMP_PURPOSE_PRESENTATION 0x20
|
||||
|
||||
// 26.4.1 LampUpdate Flags
|
||||
#define LAMP_UPDATE_FLAG_COMPLETE 0x01
|
||||
|
||||
typedef struct PACKED {
|
||||
uint8_t red;
|
||||
uint8_t green;
|
||||
uint8_t blue;
|
||||
uint8_t intensity;
|
||||
} lamp_state_t;
|
||||
|
||||
typedef struct PACKED {
|
||||
uint16_t lamp_count;
|
||||
struct {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t depth;
|
||||
} bounds;
|
||||
uint32_t kind;
|
||||
uint32_t update_interval;
|
||||
} lamparray_attributes_t;
|
||||
|
||||
typedef struct PACKED {
|
||||
uint16_t lamp_id;
|
||||
struct {
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
int32_t z;
|
||||
} position;
|
||||
int32_t update_latency;
|
||||
int32_t purposes;
|
||||
lamp_state_t levels;
|
||||
uint8_t is_programmable;
|
||||
uint8_t input_binding;
|
||||
} lamparray_attributes_response_t;
|
||||
|
||||
typedef struct PACKED {
|
||||
uint8_t flags;
|
||||
uint16_t start;
|
||||
uint16_t end;
|
||||
lamp_state_t color;
|
||||
} lamparray_range_update_t;
|
||||
|
||||
#define LAMP_MULTI_UPDATE_LAMP_COUNT 8
|
||||
typedef struct PACKED {
|
||||
uint8_t count;
|
||||
uint8_t flags;
|
||||
uint16_t ids[LAMP_MULTI_UPDATE_LAMP_COUNT];
|
||||
lamp_state_t colors[LAMP_MULTI_UPDATE_LAMP_COUNT];
|
||||
} lamparray_multi_update_t;
|
||||
|
||||
typedef struct PACKED universal_lamparray_response_t {
|
||||
uint8_t report_id;
|
||||
union {
|
||||
struct {
|
||||
uint16_t lamp_id;
|
||||
};
|
||||
struct {
|
||||
uint8_t autonomous;
|
||||
};
|
||||
lamparray_range_update_t range_update;
|
||||
lamparray_multi_update_t multi_update;
|
||||
};
|
||||
} universal_lamparray_response_t;
|
||||
|
||||
typedef struct PACKED lamparray_attributes_report_t {
|
||||
uint8_t report_id;
|
||||
lamparray_attributes_t attributes;
|
||||
} lamparray_attributes_report_t;
|
||||
|
||||
typedef struct PACKED lamparray_attributes_response_report_t {
|
||||
uint8_t report_id;
|
||||
lamparray_attributes_response_t attributes_response;
|
||||
} lamparray_attributes_response_report_t;
|
||||
|
||||
/**
|
||||
* \brief Gets LampArrayAttributesReport data
|
||||
*/
|
||||
void lamparray_get_attributes(lamparray_attributes_t* data);
|
||||
|
||||
/**
|
||||
* \brief Sets LampAttributesRequestReport data
|
||||
*/
|
||||
void lamparray_set_attributes_response(uint16_t lamp_id);
|
||||
|
||||
/**
|
||||
* \brief Gets LampAttributesResponseReport data
|
||||
*/
|
||||
void lamparray_get_attributes_response(lamparray_attributes_response_t* data);
|
||||
|
||||
/**
|
||||
* \brief Sets LampRangeUpdateReport data
|
||||
*/
|
||||
void lamparray_set_range(lamparray_range_update_t* data);
|
||||
|
||||
/**
|
||||
* \brief Sets LampMultiUpdateReport data
|
||||
*/
|
||||
void lamparray_set_items(lamparray_multi_update_t* data);
|
||||
|
||||
/**
|
||||
* \brief Sets LampArrayControlReport data
|
||||
*/
|
||||
void lamparray_set_control_response(uint8_t autonomous);
|
||||
|
||||
//****************************************************************************
|
||||
// utils
|
||||
|
||||
uint8_t lamparray_binding_at_keymap_location(uint8_t row, uint8_t col);
|
||||
|
||||
void lamparray_queue_request(universal_lamparray_response_t* report);
|
||||
|
||||
//****************************************************************************
|
||||
// feature hooks
|
||||
|
||||
void lamparray_init(void);
|
||||
|
||||
void lamparray_task(void);
|
||||
|
||||
//****************************************************************************
|
||||
// lighting framework bindings
|
||||
|
||||
void lamparray_get_lamp_impl(uint16_t lamp_id, lamparray_attributes_response_t* data);
|
||||
uint8_t lamparray_get_lamp_binding_impl(uint16_t lamp_id);
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2024 QMK
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#include "lamparray.h"
|
||||
#include "lamparray_surface.h"
|
||||
#include "rgb_matrix.h"
|
||||
|
||||
/**
|
||||
* \brief Get feature specific lamp info.
|
||||
*
|
||||
* Scales the LED config with the assumed RGB Matrix dimensions (224x64), for simplicity, as a completely flat device.
|
||||
* Assumes all keys are either on the top or bottom of the resulting rectangle.
|
||||
*/
|
||||
__attribute__((weak)) void lamparray_get_lamp_impl(uint16_t lamp_id, lamparray_attributes_response_t* data) {
|
||||
data->position.x = (LAMPARRAY_WIDTH / 224) * g_led_config.point[lamp_id].x;
|
||||
data->position.y = (LAMPARRAY_HEIGHT / 64) * g_led_config.point[lamp_id].y;
|
||||
|
||||
if (g_led_config.flags[lamp_id] & LED_FLAG_UNDERGLOW) {
|
||||
data->position.z = LAMPARRAY_DEPTH;
|
||||
data->purposes = LAMP_PURPOSE_ACCENT;
|
||||
} else if (g_led_config.flags[lamp_id] & LED_FLAG_INDICATOR) {
|
||||
data->position.z = 0;
|
||||
data->purposes = LAMP_PURPOSE_STATUS;
|
||||
} else {
|
||||
data->position.z = 0;
|
||||
data->purposes = LAMP_PURPOSE_CONTROL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Query a HID usage for a given lamp
|
||||
*/
|
||||
__attribute__((weak)) uint8_t lamparray_get_lamp_binding_impl(uint16_t lamp_id) {
|
||||
for (uint8_t i_row = 0; i_row < MATRIX_ROWS; i_row++) {
|
||||
for (uint8_t i_col = 0; i_col < MATRIX_COLS; i_col++) {
|
||||
if (g_led_config.matrix_co[i_row][i_col] == lamp_id) {
|
||||
return lamparray_binding_at_keymap_location(i_row, i_col);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: temporay binding of storage and render
|
||||
#include "rgb_matrix/overlay.c"
|
||||
|
||||
void lamparray_surface_enable(bool enable) {
|
||||
rgb_matrix_overlay_enable(enable);
|
||||
}
|
||||
|
||||
void lamparray_surface_set_item(uint16_t index, lamp_state_t color) {
|
||||
rgb_matrix_overlay_set(index, (rgba_t){color.red, color.green, color.blue, color.intensity ? 0xFF : 0});
|
||||
}
|
||||
|
||||
void lamparray_surface_update_finished(void) {
|
||||
rgb_matrix_overlay_flush();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2023 QMK
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* \brief lamparray_surface_enable
|
||||
*/
|
||||
void lamparray_surface_enable(bool enable);
|
||||
|
||||
/**
|
||||
* \brief lamparray_surface_set_item
|
||||
*/
|
||||
void lamparray_surface_set_item(uint16_t index, lamp_state_t color);
|
||||
|
||||
/**
|
||||
* \brief lamparray_surface_update_finished
|
||||
*/
|
||||
void lamparray_surface_update_finished(void);
|
||||
Reference in New Issue
Block a user